1
0
Fork 0
mirror of https://gitlab.jonasled.de/jonasled/discordbot synced 2024-11-20 18:21:28 +01:00
discordbot/bot.py

294 lines
No EOL
19 KiB
Python

#!/usr/bin/env python3
import discord
from discord.ext import commands
from discord.utils import get
from discord import FFmpegPCMAudio
import sqlite3
from time import sleep, time
from datetime import datetime, timedelta
from config import *
polls = {}
bot = commands.Bot(command_prefix=prefix)
conn = sqlite3.connect('database.db')
c = conn.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)"
c.execute(sql)
c.execute(sql2)
c.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 = c.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):
c.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
c.execute("UPDATE MESSAGES SET textXP = ? WHERE user = ? AND server = ?", [messages, message.author.id, message.guild.id]) #update the textXP value in the Database
conn.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 = c.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):
c.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
c.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 = c.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
c.execute("UPDATE poll SET calls = ? WHERE pollID = ? AND reaction = ?", [calls + 1, message.message_id, str(message.emoji)])
except:
pass
conn.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 = c.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
c.execute("UPDATE poll SET calls = ? WHERE pollID = ? AND reaction = ?", [calls - 1, message.message_id, str(message.emoji)])
except:
pass
conn.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 = c.execute("SELECT startTime FROM VcActive WHERE user=?", [member.id]).fetchone()[0]#if yes, get the join time from the database
c.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 = c.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
c.execute("UPDATE MESSAGES SET vcXP = ? WHERE user = ? AND server = ?", [result + round(xp), member.id, member.guild.id])
conn.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")
c.execute("INSERT INTO VcActive VALUES (?, ?)", [member.id, round(time())])#insert the current time in the table
conn.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 = c.execute("SELECT startTime FROM VcActive WHERE user = ?", [member.id]).fetchone()[0]#get the join time from database
c.execute("DELETE FROM VcActive WHERE user=?", [member.id])# delete the join time entry
xp = (time() - result) / 60 / minutesPerVcXP#calculate the XP
result = c.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
c.execute("UPDATE MESSAGES SET vcXP = ? WHERE user = ? AND server = ?", [result + round(xp), member.id, member.guild.id])
conn.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))
c.execute("INSERT INTO VcActive VALUES (?, ?)", [member.id, round(time())])#insert the current time in the database
conn.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 = c.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 = c.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 = c.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]) + " XP (**Text:** " + str(entries[2]) + " + **Reaction:** " + str(entries[3]) + " + **VC:** " + str(entries[4]) + ")\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
c.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 = c.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
c.execute("DELETE FROM poll where pollID = ?", [id])#delete the entries from the poll in the database
conn.commit()#write the database to disk
bot.run(token, bot=botAccount)#start the bot with the options in config.py