From 2bde1ce93652475a8131ed6d69ec21380e53bff2 Mon Sep 17 00:00:00 2001 From: phxntxm Date: Fri, 8 Jul 2016 20:27:19 -0500 Subject: [PATCH] Complete modification to use cogs --- bot.py | 638 +------------------------------- cogs/core.py | 92 +++++ cogs/interaction.py | 184 +++++++++ cogs/mod.py | 30 ++ cogs/owner.py | 110 ++++++ playlist.py => cogs/playlist.py | 44 ++- cogs/stats.py | 80 ++++ cogs/utils/checks.py | 37 ++ cogs/utils/config.py | 29 ++ 9 files changed, 605 insertions(+), 639 deletions(-) create mode 100644 cogs/core.py create mode 100644 cogs/interaction.py create mode 100644 cogs/mod.py create mode 100644 cogs/owner.py rename playlist.py => cogs/playlist.py (92%) create mode 100644 cogs/stats.py create mode 100644 cogs/utils/checks.py create mode 100644 cogs/utils/config.py diff --git a/bot.py b/bot.py index 1fa3837..118e05b 100644 --- a/bot.py +++ b/bot.py @@ -1,104 +1,35 @@ #!/usr/local/bin/python3.5 -import asyncio -import json -import os -import random -import re -import subprocess -import sys -import urllib.parse -import urllib.request -import yaml -from threading import Timer - import discord -import pymysql.cursors from discord.ext import commands +from cogs.utils import config -import playlist +extensions = ['cogs.interaction', + 'cogs.core', + 'cogs.mod', + 'cogs.owner', + 'cogs.stats', + 'cogs.playlist'] -with open("/home/phxntx5/public_html/bot/config.yml", "r") as f: - global_config = yaml.load(f) - -botDescription = global_config.get("description") -commandPrefix = global_config.get("command_prefix") - - -# Custom predicates -def isOwner(): - def predicate(ctx): - return ctx.message.author.id == ownerID - - return commands.check(predicate) - - -def isMod(): - def predicate(ctx): - return ctx.message.author.top_role.permissions.kick_members - - return commands.check(predicate) - - -def isAdmin(): - def predicate(ctx): - return ctx.message.author.top_role.permissions.manage_server - - return commands.check(predicate) - - -def isPM(): - def predicate(ctx): - return ctx.message.channel.is_private - - return commands.check(predicate) - - -def battled(): - def predicate(ctx): - return ctx.message.author == battleP2 - - return commands.check(predicate) - - -bot = commands.Bot(command_prefix=commandPrefix, description=botDescription) -music = playlist.Music(bot) -bot.add_cog(music) - - -# Turn battling off, reset users -def battlingOff(): - global battleP1 - global battleP2 - global battling - battling = False - battleP1 = "" - battleP2 = "" +bot = commands.Bot(command_prefix=config.commandPrefix, description=config.botDescription, pm_help="True") # Bot event overrides @bot.event async def on_ready(): # Change the status upon connection to the default status - game = discord.Game(name=defaultStatus, type=0) + game = discord.Game(name=config.defaultStatus, type=0) await bot.change_status(game) - cursor = connection.cursor() + cursor = config.connection.cursor() - '''success = checkSetup(cursor) - if success=="Error: default_db": - fmt = "The bot ran into an error while checking for the default database information." - await bot.send_message(determineId(ownerID),fmt) - elif success="Error: boop_db": - fmt = "The bot ran into an error while checking for the boop database information." - await bot.send_message(determineId(ownerID),fmt)''' - - cursor.execute('use {0}'.format(db_default)) + cursor.execute('use {0}'.format(config.db_default)) cursor.execute('select channel_id from restart_server where id=1') result = cursor.fetchone()['channel_id'] if int(result) != 0: - await bot.send_message(determineId(result), "I have just finished restarting!") + destination = discord.utils.find(lambda m: m.id == result, bot.get_all_channels()) + await bot.send_message(destination, "I have just finished restarting!") cursor.execute('update restart_server set channel_id=0 where id=1') - connection.commit() + config.connection.commit() @bot.event @@ -110,540 +41,7 @@ async def on_member_join(member): async def on_member_remove(member): await bot.say("{0} has left the server, I hope it wasn't because of something I said :c".format(member)) - -# Bot commands -@bot.command() -async def joke(): - try: - fortuneCommand = "/usr/bin/fortune riddles" - fortune = subprocess.check_output(fortuneCommand.split()).decode("utf-8") - await bot.say(fortune) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True) -@isOwner() -async def restart(ctx): - try: - cursor = connection.cursor() - cursor.execute('use {0}'.format(db_default)) - sql = "update restart_server set channel_id={0} where id=1".format(ctx.message.channel.id) - cursor.execute(sql) - connection.commit() - await bot.say("Restarting; see you in the next life {0}!".format(ctx.message.author.mention)) - python = sys.executable - os.execl(python, python, *sys.argv) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True) -@isOwner() -async def py(ctx): - try: - match_single = getter.findall(ctx.message.content) - match_multi = multi.findall(ctx.message.content) - if not match_single and not match_multi: - return - else: - if not match_multi: - result = eval(match_single[0]) - await bot.say("```{0}```".format(result)) - else: - def r(v): - loop.create_task(bot.say("```{0}```".format(v))) - - exec(match_multi[0]) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True) -@isOwner() -async def shutdown(ctx): - try: - fmt = 'Shutting down, I will miss you {0.author.name}' - await bot.say(fmt.format(ctx.message)) - await bot.logout() - await bot.close() - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command() -@isOwner() -async def avatar(content): - try: - file = '/home/phxntx5/public_html/bot/images/' + content - with open(file, 'rb') as fp: - await bot.edit_profile(avatar=fp.read()) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command() -@isOwner() -async def name(newNick): - try: - await bot.edit_profile(username=newNick) - await bot.say('Changed username to ' + newNick) - # Restart the bot after this, as profile changes are not immediate - python = sys.executable - os.execl(python, python, *sys.argv) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True, no_pm=True) -@isOwner() -async def leave(ctx): - try: - await bot.say('Why must I leave? Hopefully I can come back :c') - await bot.leave_server(ctx.message.server) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command() -@isMod() -async def status(*stat: str): - try: - newStatus = ' '.join(stat) - game = discord.Game(name=newStatus, type=0) - await bot.change_status(game) - await bot.say("Just changed my status to '{0}'!".format(newStatus)) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True) -@isMod() -async def say(ctx, *msg: str): - try: - msg = ' '.join(msg) - await bot.say(msg) - await bot.delete_message(ctx.message) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command() -async def urban(*msg: str): - try: - term = '+'.join(msg) - url = "http://api.urbandictionary.com/v0/define?term={}".format(term) - response = urllib.request.urlopen(url) - data = json.loads(response.read().decode('utf-8')) - if len(data['list']) == 0: - await bot.say("No result with that term!") - else: - await bot.say(data['list'][0]['definition']) - except discord.HTTPException: - await bot.say('```Error: Defintion is too long for me to send```') - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True) -async def derpi(ctx, *search: str): - try: - if len(search) > 0: - url = 'https://derpibooru.org/search.json?q=' - query = '+'.join(search) - url += query - if ctx.message.channel.id in nsfwChannels: - url += ",+explicit&filter_id=95938" - # url should now be in the form of url?q=search+terms - # Next part processes the json format, and saves the data in useful lists/dictionaries - response = urllib.request.urlopen(url) - data = json.loads(response.read().decode('utf-8')) - results = data['search'] - - if len(results) > 0: - index = random.randint(0, len(results) - 1) - randImageUrl = results[index].get('representations').get('full')[2:] - randImageUrl = 'http://' + randImageUrl - imageLink = randImageUrl.strip() - else: - await bot.say("No results with that search term, {0}!".format(ctx.message.author.mention)) - return - else: - with urllib.request.urlopen('https://derpibooru.org/images/random') as response: - imageLink = response.geturl() - url = 'https://shpro.link/redirect.php/' - data = urllib.parse.urlencode({'link': imageLink}).encode('ascii') - response = urllib.request.urlopen(url, data).read().decode('utf-8') - await bot.say(response) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True) -async def roll(ctx): - try: - num = random.randint(1, 6) - fmt = '{0.author.name} has rolled a die and got the number {1}!' - await bot.say(fmt.format(ctx.message, num)) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(no_pm=True) -@battled() -async def accept(): - try: - if not battling: - return - num = random.randint(1, 100) - fmt = battleWins[random.randint(0, len(battleWins) - 1)] - if num <= 50: - await bot.say(fmt.format(battleP1.mention, battleP2.mention)) - updateBattleRecords(battleP1, battleP2) - elif num > 50: - await bot.say(fmt.format(battleP2.mention, battleP1.mention)) - updateBattleRecords(battleP2, battleP1) - battlingOff() - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(no_pm=True) -@battled() -async def decline(): - try: - if not battling: - return - await bot.say("{0} has chickened out! {1} wins by default!".format(battleP2.mention, battleP1.mention)) - updateBattleRecords(battleP1, battleP2) - battlingOff() - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True) -async def commands(ctx): - try: - fmt = 'Hello {0.author.name}!\n\nThis is {1.user.name}! ' - fmt += 'Here is a list of commands that you have access to ' - fmt += '(please note that this only includes the commands that you have access to):' - await bot.whisper(fmt.format(ctx.message, bot)) - - fmt2 = 'I have just sent you a PM, containing all the commands you have access to {0.author.mention}!' - ocmds = 'Commands for everyone: ```' - for com, act in openCommands.items(): - ocmds += commandPrefix + com + ": " + act + "\n\n" - ocmds += '```' - await bot.whisper(ocmds) - vcmds = 'Voice Commands: ```' - for com, act in voiceCommands.items(): - vcmds += commandPrefix + com + ": " + act + "\n\n" - vcmds += '```' - await bot.whisper(vcmds) - if ctx.message.author.top_role.permissions.kick_members: - if len(modCommands) > 0: - mcmds = 'Moderator Commands: ```' - for com, act in modCommands.items(): - mcmds += commandPrefix + com + ": " + act + "\n\n" - mcmds += '```' - await bot.whisper(mcmds) - if ctx.message.author.top_role.permissions.manage_server: - if len(adminCommands) > 0: - acmds = 'Admin Commands: ```' - for com, act in adminCommands.items(): - acmds += commandPrefix + com + ": " + act + "\n\n" - acmds += '```' - await bot.whisper(acmds) - if ctx.message.author.id == ownerID: - if len(ownerCommands) > 0: - owncmds = 'Owner Commands: ```' - for com, act in ownerCommands.items(): - owncmds += commandPrefix + com + ": " + act + "\n\n" - owncmds += '```' - await bot.whisper(owncmds) - await bot.say(fmt2.format(ctx.message)) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True, no_pm=True) -async def battle(ctx): - try: - global battleP1 - global battleP2 - global battling - if battling: - return - if len(ctx.message.mentions) == 0: - await bot.say("You must mention someone in the room " + ctx.message.author.mention + "!") - return - if len(ctx.message.mentions) > 1: - await bot.say("You cannot battle more than one person at once!") - return - player2 = ctx.message.mentions[0] - if ctx.message.author.id == player2.id: - await bot.say("Why would you want to battle yourself? Suicide is not the answer") - return - if bot.user.id == player2.id: - await bot.say("I always win, don't even try it.") - return - fmt = "{0.mention} has challenged you to a battle {1.mention}\n!accept or !decline" - battleP1 = ctx.message.author - battleP2 = player2 - await bot.say(fmt.format(ctx.message.author, player2)) - t = Timer(180, battlingOff) - t.start() - battling = True - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True, no_pm=True) -async def boop(ctx): - try: - if len(ctx.message.mentions) == 0: - await bot.say("You must mention someone in the room " + ctx.message.author.mention + "!") - return - if len(ctx.message.mentions) > 1: - await bot.say("You cannot boop more than one person at once!") - return - boopee = ctx.message.mentions[0] - booper = ctx.message.author - if boopee.id == booper.id: - await bot.say("You can't boop yourself! Silly...") - return - if boopee.id == bot.user.id: - await bot.say("Why the heck are you booping me? Get away from me >:c") - return - - cursor = connection.cursor() - cursor.execute('use {0}'.format(db_boops)) - sql = "show tables like '" + str(booper.id) + "'" - cursor.execute(sql) - result = cursor.fetchone() - amount = 1 - # Booper's table exists, continue - if result is not None: - sql = "select `amount` from `" + booper.id + "` where id='" + str(boopee.id) + "'" - cursor.execute(sql) - result = cursor.fetchone() - # Boopee's entry exists, continue - if result is not None: - amount = result.get('amount') + 1 - sql = "update `" + str(booper.id) + "` set amount = " + str(amount) + " where id=" + str( - boopee.id) - cursor.execute(sql) - # Boopee does not exist, need to create the field for it - else: - sql = "insert into `" + str(booper.id) + "` (id,amount) values ('" + str(boopee.id) + "',1)" - cursor.execute(sql) - # Booper's table does not exist, need to create the table - else: - sql = "create table `" + str(booper.id) + \ - "` (`id` varchar(255) not null,`amount` int(11) not null" + \ - ",primary key (`id`)) engine=InnoDB default charset=utf8 collate=utf8_bin" - cursor.execute(sql) - sql = "insert into `" + str(booper.id) + "` (id,amount) values ('" + str(boopee.id) + "',1)" - cursor.execute(sql) - fmt = "{0.mention} has just booped you {1.mention}! That's {2} times now!" - await bot.say(fmt.format(booper, boopee, amount)) - connection.commit() - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True, no_pm=True) -async def mostboops(ctx): - try: - cursor = connection.cursor() - cursor.execute('use {0}'.format(db_boops)) - sql = "select id,amount from `{0}` where amount=(select MAX(amount) from `{0}`)".format(ctx.message.author.id) - cursor.execute(sql) - result = cursor.fetchone() - member = determineId(result.get('id')) - await bot.say("{0} you have booped {1} the most amount of times, coming in at {2} times".format( - ctx.message.author.mention, member.mention, result.get('amount'))) - connection.commit() - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -@bot.command(pass_context=True, no_pm=True) -async def mostwins(ctx): - try: - members = ctx.message.server.members - cursor = connection.cursor() - cursor.execute('use {0}'.format(db_default)) - sql = "select * from battle_records" - cursor.execute(sql) - result = cursor.fetchall() - count = 0 - fmt = [] - if result is not None: - for r in result: - member = determineId(r['id']) - if member in members: - record = r['record'] - - winAmt = int(record.split('-')[0]) - loseAmt = int(record.split('-')[1]) - percentage = winAmt / (winAmt + loseAmt) - - position = count - - indexPercentage = 0 - if count > 0: - indexRecord = re.search('\d+-\d+', fmt[position - 1]).group(0) - indexWin = int(indexRecord.split('-')[0]) - indexLose = int(indexRecord.split('-')[1]) - indexPercentage = indexWin / (indexWin + indexLose) - while position > 0 and indexPercentage < percentage: - position -= 1 - indexRecord = re.search('\d+-\d+', fmt[position - 1]).group(0) - indexWin = int(indexRecord.split('-')[0]) - indexLose = int(indexRecord.split('-')[1]) - indexPercentage = indexWin / (indexWin + indexLose) - fmt.insert(position, "{0} has a battling record of {1}".format(member.name, record)) - count += 1 - for index in range(0, len(fmt)): - fmt[index] = "{0}) {1}".format(index + 1, fmt[index]) - connection.commit() - if len(fmt) == 0: - await bot.say("```No battling records found from any members in this server```") - return - await bot.say("```{}```".format("\n".join(fmt))) - except Exception as e: - fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' - await bot.say(fmt.format(type(e).__name__, e)) - - -def determineId(ID): - if type(ID) is int: - ID = str(ID) - member = discord.utils.find(lambda m: m.id == ID, bot.get_all_members()) - if member is not None: - return member - msg = discord.utils.find(lambda m: m.id == ID, bot.messages) - if msg is not None: - return msg - server = discord.utils.find(lambda s: s.id == ID, bot.servers) - if server is not None: - return server - channel = discord.utils.find(lambda c: c.id == ID, bot.get_all_channels()) - if channel is not None: - return channel - - -def updateBattleRecords(winner, loser): - cursor = connection.cursor() - cursor.execute('use {0}'.format(db_default)) - - # Update winners records - sql = "select record from battle_records where id={0}".format(winner.id) - cursor.execute(sql) - result = cursor.fetchone() - if result is not None: - result = result['record'].split('-') - result[0] = str(int(result[0]) + 1) - sql = "update battle_records set record ='{0}' where id='{1}'".format("-".join(result), winner.id) - cursor.execute(sql) - else: - sql = "insert into battle_records (id,record) values ('{0}','1-0')".format(winner.id) - cursor.execute(sql) - - connection.commit() - - # Update losers records - sql = "select record from battle_records where id={0}".format(loser.id) - cursor.execute(sql) - result = cursor.fetchone() - if result is not None: - result = result['record'].split('-') - result[1] = str(int(result[1]) + 1) - sql = "update battle_records set record ='{0}' where id='{1}'".format('-'.join(result), loser.id) - cursor.execute(sql) - else: - sql = "insert into battle_records (id,record) values ('{0}','0-1')".format(loser.id) - cursor.execute(sql) - - connection.commit() - - -def checkSetup(cursor): - try: - cursor.execute('use {}'.format(db_default)) - except pymysql.OperationalError: - return "Error: default_db" - else: - try: - cursor.execute('describe battle_records') - except pymysql.ProgrammingError: - # battle_records does not exist, create it - sql = "create table `battle_records` (`id` varchar(32) not null,`record` varchar(32) not null," + \ - "primary key (`id`)) engine=InnoDB default charset=utf8 collate=utf8_bin" - cursor.execute(sql) - connection.commit() - try: - cursor.execute('describe restart_server') - except pymysql.ProgrammingError: - # restart_server does not exist, create it - sql = "create table `restart_server` (`id` int(11) not null auto_increment,`channel_id` varchar(32)" + \ - "not null,primary key (`id`)) engine=InnoDB default charset=utf8 collate=utf8_bin;" - cursor.execute(sql) - connection.commit() - sql = "insert into restart_server (id,channel_id) values (1,'0')" - cursor.execute(sql) - connection.commit() - try: - cursor.execute('use {}'.format(db_boops)) - except pymysql.OperationalError: - return "Error: boop_db" - - -db_default = global_config.get("db_default") -db_boops = global_config.get("db_boops") -nsfwChannels = global_config.get("nsfw_channel") -connection = pymysql.connect(host=global_config.get("db_host"), user=global_config.get("db_user"), - password=global_config.get("db_user_pass"), charset='utf8mb4', - cursorclass=pymysql.cursors.DictCursor) - -battling = False -battleP1 = None -battleP2 = None - -battleWins = global_config.get("battleWins", []) -defaultStatus = global_config.get("default_status", "") -botToken = global_config.get("bot_token", "") -ownerID = global_config.get("owner_id", "") - -modCommands = global_config.get("modCommands", {}) -adminCommands = global_config.get("adminCommands", {}) -openCommands = global_config.get("openCommands", {}) -ownerCommands = global_config.get("ownerCommands", {}) -voiceCommands = global_config.get("voiceCommands", {}) - -getter = re.compile(r'`(?!`)(.*?)`') -multi = re.compile(r'```(.*?)```', re.DOTALL) -loop = asyncio.get_event_loop() -try: - bot.run(botToken) -except: - quit() +if __name__ == '__main__': + for e in extensions: + bot.load_extension(e) + bot.run(config.botToken) diff --git a/cogs/core.py b/cogs/core.py new file mode 100644 index 0000000..20eeed3 --- /dev/null +++ b/cogs/core.py @@ -0,0 +1,92 @@ +from discord.ext import commands +from .utils import config +import discord +import subprocess +import urllib.parse +import urllib.request +import json +import random + + +class Core: + def __init__(self, bot): + self.bot = bot + + @commands.command() + async def joke(self): + """Prints a random riddle""" + try: + fortuneCommand = "/usr/bin/fortune riddles" + fortune = subprocess.check_output(fortuneCommand.split()).decode("utf-8") + await self.bot.say(fortune) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command() + async def urban(self, *msg: str): + """Pulls the top urbandictionary.com definition for a term""" + try: + term = '+'.join(msg) + url = "http://api.urbandictionary.com/v0/define?term={}".format(term) + response = urllib.request.urlopen(url) + data = json.loads(response.read().decode('utf-8')) + if len(data['list']) == 0: + await self.bot.say("No result with that term!") + else: + await self.bot.say(data['list'][0]['definition']) + except discord.HTTPException: + await self.bot.say('```Error: Definition is too long for me to send```') + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command(pass_context=True) + async def derpi(self, ctx, *search: str): + """Provides a random image from the first page of derpibooru.org for the following term""" + try: + if len(search) > 0: + url = 'https://derpibooru.org/search.json?q=' + query = '+'.join(search) + url += query + if ctx.message.channel.id in config.nsfwChannels: + url += ",+explicit&filter_id=95938" + # url should now be in the form of url?q=search+terms + # Next part processes the json format, and saves the data in useful lists/dictionaries + response = urllib.request.urlopen(url) + data = json.loads(response.read().decode('utf-8')) + results = data['search'] + + if len(results) > 0: + index = random.randint(0, len(results) - 1) + randImageUrl = results[index].get('representations').get('full')[2:] + randImageUrl = 'http://' + randImageUrl + imageLink = randImageUrl.strip() + else: + await self.bot.say("No results with that search term, {0}!".format(ctx.message.author.mention)) + return + else: + with urllib.request.urlopen('https://derpibooru.org/images/random') as response: + imageLink = response.geturl() + url = 'https://shpro.link/redirect.php/' + data = urllib.parse.urlencode({'link': imageLink}).encode('ascii') + response = urllib.request.urlopen(url, data).read().decode('utf-8') + await self.bot.say(response) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command(pass_context=True) + async def roll(self, ctx): + """Rolls a six sided die""" + try: + num = random.randint(1, 6) + fmt = '{0.message.author.name} has rolled a die and got the number {1}!' + await self.bot.say(fmt.format(ctx, num)) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + +def setup(bot): + bot.add_cog(Core(bot)) diff --git a/cogs/interaction.py b/cogs/interaction.py new file mode 100644 index 0000000..43af7e2 --- /dev/null +++ b/cogs/interaction.py @@ -0,0 +1,184 @@ +from discord.ext import commands +from .utils import checks +from .utils import config +from threading import Timer +import random + +battling = False +battleP1 = None +battleP2 = None + + +def battlingOff(): + global battleP1 + global battleP2 + global battling + battling = False + battleP1 = "" + battleP2 = "" + + +def updateBattleRecords(winner, loser): + cursor = config.connection.cursor() + cursor.execute('use {0}'.format(config.db_default)) + + # Update winners records + sql = "select record from battle_records where id={0}".format(winner.id) + cursor.execute(sql) + result = cursor.fetchone() + if result is not None: + result = result['record'].split('-') + result[0] = str(int(result[0]) + 1) + sql = "update battle_records set record ='{0}' where id='{1}'".format("-".join(result), winner.id) + cursor.execute(sql) + else: + sql = "insert into battle_records (id,record) values ('{0}','1-0')".format(winner.id) + cursor.execute(sql) + + config.connection.commit() + + # Update losers records + sql = "select record from battle_records where id={0}".format(loser.id) + cursor.execute(sql) + result = cursor.fetchone() + if result is not None: + result = result['record'].split('-') + result[1] = str(int(result[1]) + 1) + sql = "update battle_records set record ='{0}' where id='{1}'".format('-'.join(result), loser.id) + cursor.execute(sql) + else: + sql = "insert into battle_records (id,record) values ('{0}','0-1')".format(loser.id) + cursor.execute(sql) + + config.connection.commit() + + +class Interaction: + def __init__(self, bot): + self.bot = bot + + @commands.command(pass_context=True, no_pm=True) + async def battle(self, ctx): + """Challenges the mentioned user to a battle""" + try: + global battleP1 + global battleP2 + global battling + if battling: + return + if len(ctx.message.mentions) == 0: + await self.bot.say("You must mention someone in the room " + ctx.message.author.mention + "!") + return + if len(ctx.message.mentions) > 1: + await self.bot.say("You cannot battle more than one person at once!") + return + player2 = ctx.message.mentions[0] + if ctx.message.author.id == player2.id: + await self.bot.say("Why would you want to battle yourself? Suicide is not the answer") + return + if self.bot.user.id == player2.id: + await self.bot.say("I always win, don't even try it.") + return + fmt = "{0.mention} has challenged you to a battle {1.mention}\n!accept or !decline" + battleP1 = ctx.message.author + battleP2 = player2 + await self.bot.say(fmt.format(ctx.message.author, player2)) + t = Timer(180, battlingOff) + t.start() + battling = True + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command(no_pm=True) + @checks.battled(battleP2) + async def accept(self): + """Accepts the battle challenge""" + try: + if not battling: + return + num = random.randint(1, 100) + fmt = config.battleWins[random.randint(0, len(config.battleWins) - 1)] + if num <= 50: + await self.bot.say(fmt.format(battleP1.mention, battleP2.mention)) + updateBattleRecords(battleP1, battleP2) + elif num > 50: + await self.bot.say(fmt.format(battleP2.mention, battleP1.mention)) + updateBattleRecords(battleP2, battleP1) + battlingOff() + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command(no_pm=True) + @checks.battled(battleP2) + async def decline(self): + """Declines the battle challenge""" + try: + if not battling: + return + await self.bot.say("{0} has chickened out! {1} wins by default!".format(battleP2.mention, battleP1.mention)) + updateBattleRecords(battleP1, battleP2) + battlingOff() + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command(pass_context=True, no_pm=True) + async def boop(self, ctx): + """Boops the mentioned person""" + try: + if len(ctx.message.mentions) == 0: + await self.bot.say("You must mention someone in the room " + ctx.message.author.mention + "!") + return + if len(ctx.message.mentions) > 1: + await self.bot.say("You cannot boop more than one person at once!") + return + boopee = ctx.message.mentions[0] + booper = ctx.message.author + if boopee.id == booper.id: + await self.bot.say("You can't boop yourself! Silly...") + return + if boopee.id == self.bot.user.id: + await self.bot.say("Why the heck are you booping me? Get away from me >:c") + return + + cursor = config.connection.cursor() + cursor.execute('use {0}'.format(config.db_boops)) + sql = "show tables like '" + str(booper.id) + "'" + cursor.execute(sql) + result = cursor.fetchone() + amount = 1 + # Booper's table exists, continue + if result is not None: + sql = "select `amount` from `" + booper.id + "` where id='" + str(boopee.id) + "'" + cursor.execute(sql) + result = cursor.fetchone() + # Boopee's entry exists, continue + if result is not None: + amount = result.get('amount') + 1 + sql = "update `" + str(booper.id) + "` set amount = " + str(amount) + " where id=" + str( + boopee.id) + cursor.execute(sql) + # Boopee does not exist, need to create the field for it + else: + sql = "insert into `" + str(booper.id) + "` (id,amount) values ('" + str(boopee.id) + "',1)" + cursor.execute(sql) + # Booper's table does not exist, need to create the table + else: + sql = "create table `" + str(booper.id) + \ + "` (`id` varchar(255) not null,`amount` int(11) not null" + \ + ",primary key (`id`)) engine=InnoDB default charset=utf8 collate=utf8_bin" + cursor.execute(sql) + sql = "insert into `" + str(booper.id) + "` (id,amount) values ('" + str(boopee.id) + "',1)" + cursor.execute(sql) + fmt = "{0.mention} has just booped you {1.mention}! That's {2} times now!" + await self.bot.say(fmt.format(booper, boopee, amount)) + config.connection.commit() + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + +def setup(bot): + bot.add_cog(Interaction(bot)) diff --git a/cogs/mod.py b/cogs/mod.py new file mode 100644 index 0000000..d3278f6 --- /dev/null +++ b/cogs/mod.py @@ -0,0 +1,30 @@ +from discord.ext import commands +from .utils import checks + + +class Mod: + def __init__(self, bot): + self.bot = bot + + @commands.command(pass_context=True, no_pm=True) + @checks.isAdmin() + async def leave(self, ctx): + """Forces the bot to leave the server""" + try: + await self.bot.say('Why must I leave? Hopefully I can come back :c') + await self.bot.leave_server(ctx.message.server) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command(pass_context=True) + @checks.isMod() + async def say(self, ctx, *msg: str): + """Tells the bot to repeat what you say""" + try: + msg = ' '.join(msg) + await self.bot.say(msg) + await self.bot.delete_message(ctx.message) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) diff --git a/cogs/owner.py b/cogs/owner.py new file mode 100644 index 0000000..2fe975e --- /dev/null +++ b/cogs/owner.py @@ -0,0 +1,110 @@ +from discord.ext import commands +from .utils import config +from .utils import checks +import re +import os +import sys +import discord + +getter = re.compile(r'`(?!`)(.*?)`') +multi = re.compile(r'```(.*?)```', re.DOTALL) + + +class Owner: + def __init__(self, bot): + self.bot = bot + + @commands.command(pass_context=True) + @checks.isOwner() + async def restart(self, ctx): + """Forces the bot to restart""" + try: + cursor = config.connection.cursor() + cursor.execute('use {0}'.format(config.db_default)) + sql = "update restart_server set channel_id={0} where id=1".format(ctx.message.channel.id) + cursor.execute(sql) + config.connection.commit() + await self.bot.say("Restarting; see you in the next life {0}!".format(ctx.message.author.mention)) + python = sys.executable + os.execl(python, python, *sys.argv) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command(pass_context=True) + @checks.isOwner() + async def py(self, ctx): + """Executes code""" + try: + match_single = getter.findall(ctx.message.content) + match_multi = multi.findall(ctx.message.content) + if not match_single and not match_multi: + return + else: + if not match_multi: + result = eval(match_single[0]) + await self.bot.say("```{0}```".format(result)) + else: + def r(v): + config.loop.create_task(self.bot.say("```{0}```".format(v))) + + exec(match_multi[0]) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command(pass_context=True) + @checks.isOwner() + async def shutdown(self, ctx): + """Shuts the bot down""" + try: + fmt = 'Shutting down, I will miss you {0.author.name}' + await self.bot.say(fmt.format(ctx.message)) + await self.bot.logout() + await self.bot.close() + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command() + @checks.isOwner() + async def avatar(self, content): + """Changes the avatar for the bot to the filename following the command""" + try: + file = '/home/phxntx5/public_html/bot/images/' + content + with open(file, 'rb') as fp: + await self.bot.edit_profile(avatar=fp.read()) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command() + @checks.isOwner() + async def name(self, newNick): + """Changes the bot's name""" + try: + await self.bot.edit_profile(username=newNick) + await self.bot.say('Changed username to ' + newNick) + # Restart the bot after this, as profile changes are not immediate + python = sys.executable + os.execl(python, python, *sys.argv) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command() + @checks.isOwner() + async def status(self, *stat: str): + """Changes the bot's 'playing' status""" + try: + newStatus = ' '.join(stat) + game = discord.Game(name=newStatus, type=0) + await self.bot.change_status(game) + await self.bot.say("Just changed my status to '{0}'!".format(newStatus)) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + +def setup(bot): + bot.add_cog(Owner(bot)) diff --git a/playlist.py b/cogs/playlist.py similarity index 92% rename from playlist.py rename to cogs/playlist.py index 5bc8981..d27ee06 100644 --- a/playlist.py +++ b/cogs/playlist.py @@ -2,10 +2,12 @@ import asyncio import discord import traceback from discord.ext import commands +from .utils import checks if not discord.opus.is_loaded(): discord.opus.load_opus('/usr/lib64/libopus.so.0') + class VoiceEntry: def __init__(self, message, player): self.requester = message.author @@ -16,9 +18,10 @@ class VoiceEntry: fmt = '*{0.title}* uploaded by {0.uploader} and requested by {1.display_name}' duration = self.player.duration if duration: - fmt += ' [length: {0[0]}m {0[1]}s]'.format(divmod(round(duration,0), 60)) + fmt += ' [length: {0[0]}m {0[1]}s]'.format(divmod(round(duration, 0), 60)) return fmt.format(self.player, self.requester) - + + class VoiceState: def __init__(self, bot): self.current = None @@ -26,7 +29,7 @@ class VoiceState: self.bot = bot self.play_next_song = asyncio.Event() self.songs = asyncio.Queue() - self.skip_votes = set() # a set of user_ids that voted + self.skip_votes = set() # a set of user_ids that voted self.audio_player = self.bot.loop.create_task(self.audio_player_task()) def is_playing(self): @@ -56,6 +59,7 @@ class VoiceState: self.current.player.start() await self.play_next_song.wait() + class Music: """Voice related commands. Works in multiple servers at once. @@ -85,13 +89,9 @@ class Music: self.bot.loop.create_task(state.voice.disconnect()) except: pass - def isMod(): - def predicate(ctx): - return ctx.message.author.top_role.permissions.kick_members - return commands.check(predicate) - @commands.command(pass_context=True, no_pm=True) - async def join(self, ctx, *, channel : discord.Channel): + @commands.command(no_pm=True) + async def join(self, *, channel: discord.Channel): """Joins a voice channel.""" try: await self.create_voice_client(channel) @@ -121,7 +121,7 @@ class Music: return True @commands.command(pass_context=True, no_pm=True) - async def play(self, ctx, *, song : str): + async def play(self, ctx, *, song: str): """Plays a song. If there is a song currently in the queue, then it is queued until the next song is done playing. @@ -147,8 +147,8 @@ class Music: await state.songs.put(entry) @commands.command(pass_context=True, no_pm=True) - @isMod() - async def volume(self, ctx, value : int): + @checks.isMod() + async def volume(self, ctx, value: int): """Sets the volume of the currently playing song.""" state = self.get_voice_state(ctx.message.server) @@ -158,7 +158,7 @@ class Music: await self.bot.say('Set the volume to {:.0%}'.format(player.volume)) @commands.command(pass_context=True, no_pm=True) - @isMod() + @checks.isMod() async def pause(self, ctx): """Pauses the currently played song.""" state = self.get_voice_state(ctx.message.server) @@ -167,7 +167,7 @@ class Music: player.pause() @commands.command(pass_context=True, no_pm=True) - @isMod() + @checks.isMod() async def resume(self, ctx): """Resumes the currently played song.""" state = self.get_voice_state(ctx.message.server) @@ -176,7 +176,7 @@ class Music: player.resume() @commands.command(pass_context=True, no_pm=True) - @isMod() + @checks.isMod() async def stop(self, ctx): """Stops playing audio and leaves the voice channel. This also clears the queue. @@ -194,11 +194,13 @@ class Music: await state.voice.disconnect() except: pass - @commands.command(pass_context=True,no_pm=True) - async def queuelength(self,ctx): + + @commands.command(pass_context=True, no_pm=True) + async def queuelength(self, ctx): """Prints the length of the queue""" try: - await self.bot.say("There are a total of {} songs in the queue".format(str(self.get_voice_state(ctx.message.server).songs.qsize()))) + await self.bot.say("There are a total of {} songs in the queue" + .format(str(self.get_voice_state(ctx.message.server).songs.qsize()))) except: await self.bot.say(traceback.format_exc()) @@ -229,7 +231,7 @@ class Music: await self.bot.say('You have already voted to skip this song.') @commands.command(pass_context=True, no_pm=True) - @isMod() + @checks.isMod() async def modskip(self, ctx): """Forces a song skip, can only be used by a moderator""" state = self.get_voice_state(ctx.message.server) @@ -250,3 +252,7 @@ class Music: else: skip_count = len(state.skip_votes) await self.bot.say('Now playing {} [skips: {}/3]'.format(state.current, skip_count)) + + +def setup(bot): + bot.add_cog(Music(bot)) diff --git a/cogs/stats.py b/cogs/stats.py new file mode 100644 index 0000000..12019e6 --- /dev/null +++ b/cogs/stats.py @@ -0,0 +1,80 @@ +from discord.ext import commands +from discord.utils import find +from .utils import config +import re + + +class Stats: + def __init__(self, bot): + self.bot = bot + + @commands.command(pass_context=True, no_pm=True) + async def mostboops(self, ctx): + """Shows the person you have 'booped' the most, as well as how many times""" + try: + cursor = config.connection.cursor() + cursor.execute('use {0}'.format(config.db_boops)) + sql = "select id,amount from `{0}` where amount=(select MAX(amount) from `{0}`)"\ + .format(ctx.message.author.id) + cursor.execute(sql) + result = cursor.fetchone() + member = find(lambda m: m.id == result.get('id'), self.bot.get_all_members()) + await self.bot.say("{0} you have booped {1} the most amount of times, coming in at {2} times".format( + ctx.message.author.mention, member.mention, result.get('amount'))) + config.connection.commit() + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + @commands.command(pass_context=True, no_pm=True) + async def mostwins(self, ctx): + """Prints a 'leaderboard' of everyone in the server's battling record""" + try: + members = ctx.message.server.members + cursor = config.connection.cursor() + cursor.execute('use {0}'.format(config.db_default)) + sql = "select * from battle_records" + cursor.execute(sql) + result = cursor.fetchall() + count = 0 + fmt = [] + if result is not None: + for r in result: + member = self.bot.determineId(r['id']) + if member in members: + record = r['record'] + + winAmt = int(record.split('-')[0]) + loseAmt = int(record.split('-')[1]) + percentage = winAmt / (winAmt + loseAmt) + + position = count + + indexPercentage = 0 + if count > 0: + indexRecord = re.search('\d+-\d+', fmt[position - 1]).group(0) + indexWin = int(indexRecord.split('-')[0]) + indexLose = int(indexRecord.split('-')[1]) + indexPercentage = indexWin / (indexWin + indexLose) + while position > 0 and indexPercentage < percentage: + position -= 1 + indexRecord = re.search('\d+-\d+', fmt[position - 1]).group(0) + indexWin = int(indexRecord.split('-')[0]) + indexLose = int(indexRecord.split('-')[1]) + indexPercentage = indexWin / (indexWin + indexLose) + fmt.insert(position, "{0} has a battling record of {1}".format(member.name, record)) + count += 1 + for index in range(0, len(fmt)): + fmt[index] = "{0}) {1}".format(index + 1, fmt[index]) + config.connection.commit() + if len(fmt) == 0: + await self.bot.say("```No battling records found from any members in this server```") + return + await self.bot.say("```{}```".format("\n".join(fmt))) + except Exception as e: + fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' + await self.bot.say(fmt.format(type(e).__name__, e)) + + +def setup(bot): + bot.add_cog(Stats(bot)) diff --git a/cogs/utils/checks.py b/cogs/utils/checks.py new file mode 100644 index 0000000..e74725c --- /dev/null +++ b/cogs/utils/checks.py @@ -0,0 +1,37 @@ +from discord.ext import commands +from . import config + + +def isOwner(): + def predicate(ctx): + return ctx.message.author.id == config.ownerID + + return commands.check(predicate) + + +def isMod(): + def predicate(ctx): + return ctx.message.author.top_role.permissions.kick_members + + return commands.check(predicate) + + +def isAdmin(): + def predicate(ctx): + return ctx.message.author.top_role.permissions.manage_server + + return commands.check(predicate) + + +def isPM(): + def predicate(ctx): + return ctx.message.channel.is_private + + return commands.check(predicate) + + +def battled(battleP2=""): + def predicate(ctx): + return ctx.message.author == battleP2 + + return commands.check(predicate) diff --git a/cogs/utils/config.py b/cogs/utils/config.py new file mode 100644 index 0000000..fcbddcc --- /dev/null +++ b/cogs/utils/config.py @@ -0,0 +1,29 @@ +import yaml +import pymysql.cursors +import asyncio + +loop = asyncio.get_event_loop() + +with open("/home/phxntx5/public_html/Bonfire/config.yml", "r") as f: + global_config = yaml.load(f) + +db_default = global_config.get("db_default") +db_boops = global_config.get("db_boops") +nsfwChannels = global_config.get("nsfw_channel") +connection = pymysql.connect(host=global_config.get("db_host"), user=global_config.get("db_user"), + password=global_config.get("db_user_pass"), charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor) + +botDescription = global_config.get("description") +commandPrefix = global_config.get("command_prefix") + +battleWins = global_config.get("battleWins", []) +defaultStatus = global_config.get("default_status", "") +botToken = global_config.get("bot_token", "") +ownerID = global_config.get("owner_id", "") + +modCommands = global_config.get("modCommands", {}) +adminCommands = global_config.get("adminCommands", {}) +openCommands = global_config.get("openCommands", {}) +ownerCommands = global_config.get("ownerCommands", {}) +voiceCommands = global_config.get("voiceCommands", {})