#!/usr/bin/env python3 import discord from discord.ext import commands from discord.utils import get from discord import FFmpegPCMAudio from sqlite3 import connect from time import sleep, time from datetime import datetime, timedelta from config import * polls = {} 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)) @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 @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 @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 @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="joins your current voicechat and makes a wrong sound") async def wrong(ctx):#this function joins the current voicechat of the user and plays the "wrong.mp3" file channel = ctx.author.voice.channel#get the current voicechat the user is in await channel.connect()#connect to this chat voice = ctx.voice_client#make a voice client variable (needed to playx audio) voice.play(discord.FFmpegPCMAudio("wrong.mp3"))#start to play the audio file while voice.is_playing():#wait until the file is finished sleep(0.1)#if we don't wait here for a moment the bot starts lagging await ctx.message.guild.voice_client.disconnect()#if the file is finished playing, leave the voicechat @bot.command(pass_context=True, brief="joins your current voicechat and makes a dock sound") async def duck(ctx):#this function joins the current voicechat of the user and plays the "duck.mp3" file channel = ctx.author.voice.channel#get the current voicechat the user is in await channel.connect()#connect to this chat voice = ctx.voice_client#make a voice client variable (needed to playx audio) voice.play(discord.FFmpegPCMAudio("duck.mp3"))#start to play the audio file while voice.is_playing():#wait until the file is finished sleep(0.1)#if we don't wait here for a moment the bot starts lagging await ctx.message.guild.voice_client.disconnect()#if the file is finished playing, leave the voicechat @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.run(token, bot=botAccount)#start the bot with the options in config.py