#!/usr/bin/env python3 import discord from discord.ext import commands, tasks from discord.utils import get from discord import FFmpegPCMAudio from sqlite3 import connect from datetime import datetime, timedelta from config import * from requests import get from json import loads from os.path import isfile from os import rename, remove from youtube_dl import YoutubeDL from time import time import asyncio polls = {} player = None #the Player for the music Bot playerServer = None #The Server the player is active at the moment downloadQue = [] playerQue = [] titleQue = [] fileIndex = 0 playing = False bot = commands.Bot(command_prefix=prefix) dbconnection = connect('database.db') dbcursor = dbconnection.cursor() try: #try to create the database tables, if it fails they were created previosly sql = "CREATE TABLE MESSAGES(user LONG, server LONG, textXP LONG, reactionXP LONG, vcXP LONG)" sql2 = "CREATE TABLE VcActive (user LONG, startTime LONG)" sql3 = "CREATE TABLE poll (pollID LONG, reaction STRING, calls LONG)" dbcursor.execute(sql) dbcursor.execute(sql2) dbcursor.execute(sql3) except: pass @bot.event #print the username and id to the console and change the game status async def on_ready(): print('Logged in as') print(bot.user.name) print(bot.user.id) print('------') await bot.change_presence(activity=discord.Game(name=game)) # # # _ __ ___ __ __ _ __ ___ ___ ___ ___ __ _ __ _ ___ # | '_ \ / _ \\ \ /\ / / | '_ ` _ \ / _ \/ __|/ __| / _` | / _` | / _ \ # | | | || __/ \ V V / | | | | | || __/\__ \\__ \| (_| || (_| || __/ # |_| |_| \___| \_/\_/ |_| |_| |_| \___||___/|___/ \__,_| \__, | \___| # __/ | # |___/ @bot.event async def on_message(message):#this will run on every message print(str(message.author) + " sent: " + str(message.content) + ", latency: " + str(bot.latency) + " ms, Bot: " + str(message.author.bot)) #print some information about the message to the console if(message.author.bot) or (message.author.id == bot.user.id): #check if the bot has written the message, if yes stop parsing it return res = dbcursor.execute("SELECT textXP FROM MESSAGES WHERE user=? AND server = ?", [message.author.id, message.guild.id]) #try to get the current textXP of the user result = res.fetchone() if (result == None): dbcursor.execute("INSERT INTO MESSAGES VALUES (?, ?, ?, 0, 0)", [message.author.id, message.guild.id, messageXP]) #if Bot can't find the user in the Database create a new entry else: messages = result[0] + messageXP dbcursor.execute("UPDATE MESSAGES SET textXP = ? WHERE user = ? AND server = ?", [messages, message.author.id, message.guild.id]) #update the textXP value in the Database dbconnection.commit() #write the database changes to disk if message.content.startswith(prefix): #check if the message starts with the prefix, if yes process the command await bot.process_commands(message) if "<@!" + str(bot.user.id) + ">" in message.content: if (message.content[:3] == "hi ") or (message.content[-3:] == " hi"): await message.channel.send("Hello <@!" + str(message.author.id) + "> To get a list of my commands enter " + prefix + "help.") # _ _ _ _ # | | (_) | | | | # _ __ ___ __ _ ___ | |_ _ ___ _ __ __ _ __| | __| | # | '__| / _ \ / _` | / __|| __|| | / _ \ | '_ \ / _` | / _` | / _` | # | | | __/| (_| || (__ | |_ | || (_) || | | | | (_| || (_| || (_| | # |_| \___| \__,_| \___| \__||_| \___/ |_| |_| \__,_| \__,_| \__,_| # # @bot.event async def on_raw_reaction_add(message): #this runs on every reaction if(message.user_id == bot.user.id):#check if the bot has written the message, if yes stop parsing it return res = dbcursor.execute("SELECT reactionXP FROM MESSAGES WHERE user=? AND server = ?", [message.user_id, message.guild_id]) #try to get the reactionXP from the database result = res.fetchone() if (result == None): dbcursor.execute("INSERT INTO MESSAGES VALUES (?, ?, 0, ?, 0)", [message.user_id, message.guild_id, reactionXP]) #if bot can't find the database entry for the user create a new one messages = reactionXP else: messages = result[0] + reactionXP dbcursor.execute("UPDATE MESSAGES SET reactionXP = ? WHERE user = ? AND server = ?", [messages, message.user_id, message.guild_id])#update the reactionXP with the new values try: polls[str(message.message_id)] # check if the reaction is on a poll calls = dbcursor.execute("SELECT calls FROM poll WHERE pollID = ? AND reaction = ?", [message.message_id, str(message.emoji)]).fetchone()[0]# if yes update the database and add 1 vote dbcursor.execute("UPDATE poll SET calls = ? WHERE pollID = ? AND reaction = ?", [calls + 1, message.message_id, str(message.emoji)]) except: pass dbconnection.commit() #write the database changes to disk # _ _ # | | (_) # _ __ ___ __ _ ___ | |_ _ ___ _ __ _ __ ___ _ __ ___ ___ __ __ ___ # | '__| / _ \ / _` | / __|| __|| | / _ \ | '_ \ | '__| / _ \| '_ ` _ \ / _ \ \ \ / / / _ \ # | | | __/| (_| || (__ | |_ | || (_) || | | | | | | __/| | | | | || (_) | \ V / | __/ # |_| \___| \__,_| \___| \__||_| \___/ |_| |_| |_| \___||_| |_| |_| \___/ \_/ \___| # # @bot.event async def on_raw_reaction_remove(message): try: polls[str(message.message_id)] #check if the message is a poll calls = dbcursor.execute("SELECT calls FROM poll WHERE pollID = ? AND reaction = ?", [message.message_id, str(message.emoji)]).fetchone()[0]#if yes update the database and remove 1 vote dbcursor.execute("UPDATE poll SET calls = ? WHERE pollID = ? AND reaction = ?", [calls - 1, message.message_id, str(message.emoji)]) except: pass dbconnection.commit()# write the database changes to disk # _ _ _ _ _ # (_) | | | | | | | | # __ __ ___ _ ___ ___ ___ | |__ __ _ | |_ _ _ _ __ __| | __ _ | |_ ___ # \ \ / / / _ \ | | / __| / _ \ / __|| '_ \ / _` || __| | | | || '_ \ / _` | / _` || __| / _ \ # \ V / | (_) || || (__ | __/| (__ | | | || (_| || |_ | |_| || |_) || (_| || (_| || |_ | __/ # \_/ \___/ |_| \___| \___| \___||_| |_| \__,_| \__| \__,_|| .__/ \__,_| \__,_| \__| \___| # | | # |_| @bot.event async def on_voice_state_update(member, before, after):#this runs on every voice chat update (user joins, leaves, go afk, moves, ...) if member.bot or (member.id == bot.user.id): #check if the bot is the user, if yes stop parsing it return if not before.afk and after.afk:#check if the user moved from not afk to afk print(str(member) + " is now AFK") result = dbcursor.execute("SELECT startTime FROM VcActive WHERE user=?", [member.id]).fetchone()[0]#if yes, get the join time from the database dbcursor.execute("DELETE FROM VcActive WHERE user=?", [member.id])#delete the entrie from the voicechat in the database xp = (time() - result) / minutesPerVcXP#calculate the xp result = dbcursor.execute("SELECT vcXP FROM MESSAGES WHERE user=? AND server = ?", [member.id, member.guild.id]).fetchone()[0]#update the user database and set the new Voice XP dbcursor.execute("UPDATE MESSAGES SET vcXP = ? WHERE user = ? AND server = ?", [result + round(xp), member.id, member.guild.id]) dbconnection.commit() #write the changes to the database return if before.afk and not after.afk: #check if the user moved from afk back to active print(str(member) + " is active again") dbcursor.execute("INSERT INTO VcActive VALUES (?, ?)", [member.id, round(time())])#insert the current time in the table dbconnection.commit() #write the changes to database return if(after.channel == None): #check if the user left the voicechat print(str(member) + " left the Voicechat") result = dbcursor.execute("SELECT startTime FROM VcActive WHERE user = ?", [member.id]).fetchone()[0]#get the join time from database dbcursor.execute("DELETE FROM VcActive WHERE user=?", [member.id])# delete the join time entry xp = (time() - result) / 60 / minutesPerVcXP#calculate the XP result = dbcursor.execute("SELECT vcXP FROM MESSAGES WHERE user=? AND server = ?", [member.id, member.guild.id]).fetchone()[0]#update the user Table and set the new voiceXP dbcursor.execute("UPDATE MESSAGES SET vcXP = ? WHERE user = ? AND server = ?", [result + round(xp), member.id, member.guild.id]) dbconnection.commit()#write the changes to disk return elif(before.channel == None): #check if a user joins the voicechat print(str(member) + " joined the Voicechat " + str(after.channel)) dbcursor.execute("INSERT INTO VcActive VALUES (?, ?)", [member.id, round(time())])#insert the current time in the database dbconnection.commit()#write the changes to databasr return # _ _ # | | | | # | | ___ __ __ ___ | | # | | / _ \\ \ / / / _ \| | # | || __/ \ V / | __/| | # |_| \___| \_/ \___||_| # # @bot.command(brief="shows your current XP and level") async def level(ctx, user : discord.Member=None):#shows your current XP and level, as argument you can give a different user if (user == None): #check if the message author submitted a different user to check, if not use the author user = ctx.message.author xp = dbcursor.execute("SELECT textXP, reactionXP, vcXP FROM MESSAGES WHERE user=? AND server = ?", [user.id, ctx.message.guild.id]).fetchone()#get the xp of the user from database unsorted = dbcursor.execute("SELECT user, textXP, reactionXP, vcXP FROM MESSAGES WHERE server = ?", [ctx.message.guild.id]).fetchall() #get all users from the database (for the ranking) ranking = [] #to this variable we will later add the users sorted while len(unsorted) > 0: #do this while we have entries in unsorted lowest = unsorted[0]#just set the first entrie in lowest, so we can later overwrite it if we find a user with less XP for entries in unsorted: #go through every user, calculate the XP and check if its lower than the current lowest if((entries[1] + entries[2] + entries[3]) <= (lowest[1] + lowest[2] + lowest[3])): lowest = entries#if the xp is lower, write the user to lowest unsorted.remove(lowest)#after checking every user, remove the lowest from unsorted and add it to ranking ranking.append(lowest) for entries in ranking:#try to find the user in the ranking array, to get his rank. if(user.id == entries[0]): rank = len(ranking) - ranking.index(entries) tempXP = xp[0]+xp[1]+xp[2]#the XP the user has in this level level = 0#the current level levelXP = 0#the XP the user needs for the level while (tempXP > 0): #while the XP is over 0, calculate the XP needed fo this level and subtract it from tempXP levelXP = base * (factor**(level)) tempXP = tempXP - levelXP level = level + 1 level = level - 1 #subtract one level, because the last level the user didn't reached username = str(user.name)#get the username embed = discord.Embed(title="Stats of " + username , description="Rank " + str(rank) + " of " + str(len(ranking)))#make the response message embed.set_thumbnail(url=user.avatar_url) embed.add_field(name="Text XP", value=xp[0]) embed.add_field(name="Reaction XP", value=xp[1]) embed.add_field(name="Voice XP", value=xp[2]) embed.add_field(name="Total XP", value=xp[0]+xp[1]+xp[2]) embed.add_field(name="Level", value=level) embed.add_field(name="Progress", value=str(round(levelXP+tempXP)) + "/" + str(round(levelXP)) + " (" + str(round((levelXP+tempXP) / levelXP * 100 )) + "%)") await ctx.message.channel.send(embed=embed) #send the response message # _ _ _ _ # | | | | | | | | # | | ___ __ _ __| | ___ _ __ | |__ ___ __ _ _ __ __| | # | | / _ \ / _` | / _` | / _ \| '__|| '_ \ / _ \ / _` || '__| / _` | # | || __/| (_| || (_| || __/| | | |_) || (_) || (_| || | | (_| | # |_| \___| \__,_| \__,_| \___||_| |_.__/ \___/ \__,_||_| \__,_| # # @bot.command(brief="shows the leaderboard") async def leaderboard(ctx): unsorted = dbcursor.execute("SELECT user, textXP, reactionXP, vcXP FROM MESSAGES WHERE server = ?", [ctx.message.guild.id]).fetchall() #get all users from the database (for the ranking) ranking = [] #to this variable we will later add the users sorted while len(unsorted) > 0: #do this while we have entries in unsorted lowest = unsorted[0]#just set the first entrie in lowest, so we can later overwrite it if we find a user with less XP for entries in unsorted: #go through every user, calculate the XP and check if its lower than the current lowest if((entries[1] + entries[2] + entries[3]) <= (lowest[1] + lowest[2] + lowest[3])): lowest = entries#if the xp is lower, write the user to lowest unsorted.remove(lowest)#after checking every user, remove the lowest from unsorted and add it to ranking ranking.append(lowest) while len(ranking) > 10:#remove the last user, while the length is over 10 ranking.remove(ranking[0]) badges = [":first_place: ", ":second_place: ", ":third_place: ", ":four: ", ":five: ", ":six: ", ":seven: ", ":eight: ", ":nine: ", ":keycap_ten: "]#the emoji in front of the username message = ""#in this variable we will prepare in the next step the response for entries in reversed(ranking):#loop through every user in reverse order to get the best first and andd his statistics to message message = message + badges[len(ranking) - ranking.index(entries) - 1] + ": <@" + str(entries[0]) + "> **Total:** " + str(entries[1] + entries[2] + entries[3]) + " XP (**Text:** " + str(entries[1]) + " + **Reaction:** " + str(entries[2]) + " + **VC:** " + str(entries[3]) + ")\n" embed = discord.Embed(title="Leaderboard" , description=message)#make the response Box await ctx.message.channel.send(embed=embed)#print the ressponse box # _ # (_) # _ __ _ _ __ __ _ # | '_ \ | || '_ \ / _` | # | |_) || || | | || (_| | # | .__/ |_||_| |_| \__, | # | | __/ | # |_| |___/ @bot.command(pass_context=True, brief="prints the ping time of the bot") async def ping(ctx):#prints the ping and the time the bot needed to process this command now = datetime.utcnow()#get the current time delta = round((now.microsecond - ctx.message.created_at.microsecond) /1000)#substract the time the message was created from the current time 8in microsecconds), convert this to millisecconds and round embed = discord.Embed(title=":ping_pong: | Pong!", description="```prolog\nLatency:: " + str(round(bot.latency * 1000)) + "ms\nResponse :: " + str(delta) + "ms```")#make the response, we format it as code and select prolog as language for nice cloloring await ctx.message.channel.send(embed=embed)#send the prepared message # _ _ __ _ _ # (_)(_) / _| | | | | # ___ _ __ ___ ___ _ _ | |_ _ _ | |_ ___ __ __| |_ # / _ \| '_ ` _ \ / _ \ | || || _|| | | | | __| / _ \\ \/ /| __| # | __/| | | | | || (_) | | || || | | |_| | | |_ | __/ > < | |_ # \___||_| |_| |_| \___/ | ||_||_| \__, | \__| \___|/_/\_\ \__| # _/ | __/ | # |__/ |___/ @bot.command(brief="prints the text as emojies") async def emoji(ctx, *message: str):#emojifies the string the user give as parameter temp = "" for messages in message:#because every word will be formatted as parameter by discord.py, we have to make a long string again temp = temp + messages + " " messageNew = "" for char in temp:#process every character one by one char = char.lower()#convert it to lower (e.g. Aa ==> aa) if char in "abcdefghijklmnopqrstuvwxyz":#check if the char is a letter messageNew = messageNew + ":regional_indicator_" + char + ":"#if yes we add to the new message the emoji of the letter elif char in "1234567890":#if the character is a number we have to use a different emoji numbers = { "0" : 'zero', "1" : 'one', "2" : 'two', "3" : 'three', "4" : 'four', "5": 'five', "6" : 'six', "7" : 'seven', "8" : 'eight', "9" : 'nine',} messageNew = messageNew + ":" + numbers[char] + ":" elif char == "?":#the emoji for ? messageNew = messageNew + ":question:" elif char == "!":#the emoji for ! messageNew = messageNew + ":exclamation:" else:#if we can'T find a suitable emoji, just add the raw char messageNew = messageNew + char await ctx.message.channel.send(messageNew)#send the formatted message # _ _ # | || | # _ __ ___ | || | # | '_ \ / _ \ | || | # | |_) || (_) || || | # | .__/ \___/ |_||_| # | | # |_| @bot.command(pass_context=True, brief="starts a poll", description="starts a poll, please give as first argument the question (for sentences please use \") and then the posibilities (leave empty for yes or no)") async def poll(ctx, question, *options: str):#this is a tool to create a poll if len(options) <= 1:#check if options were supplied, if not add yes and no to the options options = list(options) options.append("yes") options.append("no") options = tuple(options) if len(options) > 9:#we can only process 9 arguments at the moment (emoji 1 to 9) await ctx.message.channel.send('You cannot make a poll for more than 9 things!')#if the user added more arguments, print a error message return if len(options) == 2 and options[0] == 'yes' and options[1] == 'no':#select the reaction emojis, ✅ and ❌ for yes or no and numbers one to 9 for the rest reactions = ['✅', '❌'] else: reactions = ['1⃣', '2⃣', '3⃣', '4⃣', '5⃣', '6⃣', '7⃣', '8⃣', '9⃣'] description = "" for x, option in enumerate(options):#make a dict from options and process every entry, x is the key and option the value description += '\n {} {}'.format(reactions[x], option)#add to the description for every entry in otions one line with the emoji and the answer embed = discord.Embed(title=question, description=description)#prepare the response await ctx.message.channel.send("@everyone")#tag everyone, for the new survey react_message = await ctx.message.channel.send(embed=embed)#print the survey and save the message to a variable polls[str(react_message.id)] = react_message#save the react message to the polls array to acces it later for reaction in reactions[:len(options)]:#for every reaction add an entry to the reaction database and add it to the message, that the user can click it dbcursor.execute("INSERT INTO poll VALUES (?, ?, 0)", [react_message.id, reaction]) await react_message.add_reaction(reaction) embed.set_footer(text='Poll ID: {}'.format(react_message.id))#add the poll id to the message (needed to get the results) await react_message.edit(embed=embed) # _ _ _ _ # | || | | || | # _ __ ___ | || | _ __ ___ ___ _ _ | || |_ # | '_ \ / _ \ | || | | '__| / _ \/ __|| | | || || __| # | |_) || (_) || || | | | | __/\__ \| |_| || || |_ # | .__/ \___/ |_||_| |_| \___||___/ \__,_||_| \__| # | | # |_| @bot.command(pass_context=True, brief="shows the result of a poll, give the poll ID as argument") async def result(ctx, id):#this function prints the result of a poll poll_message = polls[str(id)]#get the poll message from the variable embed = poll_message.embeds[0]#get the embed (the content) del polls[str(id)]#delte the poll from the poll message array unformatted_options = [x.strip() for x in poll_message.embeds[0].description.split('\n')]#split the poll options (split every line and remove all leading and trailing spaces) opt_dict = {} for options in unformatted_options:#for every option: split on the space between emoji and text and put it in a dict, with the emoji as key and the emoji + the text as value options = options.split(" ", 1) opt_dict[options[0]] = options[0] + " " + options[1] request = dbcursor.execute("SELECT calls, reaction FROM poll WHERE pollID = ?", [id]).fetchall() #get the votes from the database description = "" for results in request:#loop through the entries of the database and make the result description = description + "\n" + opt_dict[results[1]] + ": " + str(results[0]) + " votes" embed = discord.Embed(title=poll_message.embeds[0].title, description=description)#prepare the result embed.set_footer(text='enden on ' + datetime.now().strftime("%d.%m.%Y %H:%M:%S"))#set as footer the end time of the poll await poll_message.edit(embed=embed)#edit the origina lmessage and print the results embed2 = discord.Embed(title=" ", description="You can find the result of the poll in the original message [here](" + poll_message.jump_url + ").")#This message will be printed to the end of the chat with a link to the original (where the results are) await ctx.message.channel.send(embed=embed2)#print the result message from one line up dbcursor.execute("DELETE FROM poll where pollID = ?", [id])#delete the entries from the poll in the database dbconnection.commit()#write the database to disk # ___ ___ _ ______ _ # | \/ | (_) | ___ \ | | # | . . | _ _ ___ _ ___ | |_/ / ___ | |_ # | |\/| || | | |/ __|| | / __|| ___ \ / _ \ | __| # | | | || |_| |\__ \| || (__ | |_/ /| (_) || |_ # \_| |_/ \__,_||___/|_| \___|\____/ \___/ \__| # # @bot.command(brief="adds a song to the playlist") async def play(ctx, songURL : str): global player global playerServer global downloadQue try: channel = ctx.author.voice.channel#get the current voicechat the user is in except: await ctx.message.channel.send("To use the music bot you have to be in a voice chat") return if(player == None): await channel.connect()#connect to this chat player = ctx.voice_client #save the voice client for later use playerServer = ctx.message.guild.id#save the music bot server id to variable if(playerServer != ctx.message.guild.id):#check if the music bot is at the moment on this server await ctx.message.channel.send("The Bot is currently connected to a different channel, please wait until he is finished there") return r = get("https://www.youtube.com/oembed?format=json&url=" + songURL) if(r.status_code != 200): await ctx.message.channel.send("Can't find the song") return await ctx.message.channel.send("Adding `" + loads(r.text)["title"] + "` to que.") downloadQue.append([loads(r.text)["html"].split("src=")[1][1:].split("\"")[0], loads(r.text)["title"]]) @bot.command(brief="skipps the current song in playlist") async def skip(ctx): global player player.stop() await ctx.message.channel.send("Skipped to next song") @bot.command(brief="Prints the current que") async def que(ctx): global titleQue message = "**Musicbot Que:**\n **[current]** " + titleQue[0] for entries in titleQue[1:]: message = message + "\n" + entries await ctx.message.channel.send(message) async def checkForNextSong(): global player global playerQue global playerServer global playing global titleQue while True: if(player != None) and not player.is_playing() and (len(playerQue) > 0): if(playing): playerQue.remove(playerQue[0]) titleQue.remove(titleQue[0]) playing = False if(len(playerQue) == 0): print("que is empty") await player.disconnect() player = None playerServer = None else: playing = True print("playing next song") player.play(discord.FFmpegPCMAudio(playerQue[0])) await asyncio.sleep(1) async def downloadAudioFiles(): global downloadQue global fileIndex while True: if(len(downloadQue) > 0): ydl_opts = { #youtube-dl arguments 'format': 'worstaudio/worst', 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192', }], 'outtmpl': str(fileIndex) +'.%(ext)s', } with YoutubeDL(ydl_opts) as ydl: ydl.download([downloadQue[0][0]]) playerQue.append(str(fileIndex) + ".mp3") titleQue.append(downloadQue[0][1]) downloadQue.remove(downloadQue[0]) fileIndex = fileIndex + 1 await asyncio.sleep(1) bot.loop.create_task(checkForNextSong()) bot.loop.create_task(downloadAudioFiles()) bot.run(token, bot=botAccount)#start the bot with the options in config.py