2022-02-05 21:24:08 +01:00
|
|
|
|
from discord import Interaction
|
2021-09-03 20:40:03 +02:00
|
|
|
|
|
2020-10-13 21:02:40 +02:00
|
|
|
|
from data import constants
|
2021-06-30 21:55:17 +02:00
|
|
|
|
from data.snipe import Snipe, Action, should_snipe
|
2020-10-13 21:02:40 +02:00
|
|
|
|
import datetime
|
|
|
|
|
import discord
|
|
|
|
|
from discord.ext import commands
|
2021-09-03 20:40:03 +02:00
|
|
|
|
from functions import checks, easterEggResponses, stringFormatters
|
2021-06-19 17:45:26 +02:00
|
|
|
|
from functions.database import stats, muttn, custom_commands, commands as command_stats
|
2020-10-13 21:02:40 +02:00
|
|
|
|
import pytz
|
2021-09-05 16:09:10 +02:00
|
|
|
|
from settings import READY_MESSAGE, SANDBOX
|
2021-06-22 00:36:22 +02:00
|
|
|
|
from startup.didier import Didier
|
2020-10-13 21:02:40 +02:00
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Events(commands.Cog):
|
|
|
|
|
|
2021-06-22 00:36:22 +02:00
|
|
|
|
def __init__(self, client: Didier):
|
|
|
|
|
self.client: Didier = client
|
2020-10-13 21:02:40 +02:00
|
|
|
|
self.utilsCog = self.client.get_cog("Utils")
|
|
|
|
|
self.failedChecksCog = self.client.get_cog("FailedChecks")
|
|
|
|
|
self.lastFeatureRequest = 0
|
|
|
|
|
self.lastBugReport = 0
|
|
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
|
|
|
|
async def on_connect(self):
|
|
|
|
|
"""
|
|
|
|
|
Function called when the bot connects to Discord.
|
|
|
|
|
"""
|
|
|
|
|
print("Connected")
|
|
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
|
|
|
|
async def on_ready(self):
|
|
|
|
|
"""
|
|
|
|
|
Function called when the bot is ready & done leading.
|
|
|
|
|
"""
|
2021-06-19 20:11:55 +02:00
|
|
|
|
print(READY_MESSAGE)
|
2020-10-13 21:02:40 +02:00
|
|
|
|
|
|
|
|
|
# Add constants to the client as a botvar
|
2021-06-19 20:11:55 +02:00
|
|
|
|
self.client.constants = constants.Live if SANDBOX else constants.Zandbak
|
2020-10-13 21:02:40 +02:00
|
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
|
|
|
|
async def on_message(self, message):
|
|
|
|
|
"""
|
|
|
|
|
Function called when someone sends a message the bot can see.
|
|
|
|
|
:param message: the discord.Message instance of the message
|
|
|
|
|
"""
|
|
|
|
|
# Check if the server is locked, if so only allow me (to unlock) & Didier (to send the message) to talk
|
|
|
|
|
if self.client.locked \
|
|
|
|
|
and message.guild is not None \
|
|
|
|
|
and str(message.author.id) != constants.myId \
|
|
|
|
|
and str(message.author.id) != constants.didierId:
|
|
|
|
|
# Auto unlock when someone sends a message past the current time
|
|
|
|
|
if time.time() > self.client.lockedUntil:
|
|
|
|
|
return await self.unlock(message.channel)
|
|
|
|
|
|
|
|
|
|
return await self.utilsCog.removeMessage(message)
|
|
|
|
|
|
|
|
|
|
# If FreeGamesCheck failed, remove the message & send the user a DM
|
|
|
|
|
if not checks.freeGamesCheck(message):
|
|
|
|
|
await self.failedChecksCog.freeGames(message)
|
|
|
|
|
|
|
|
|
|
# Boos React to people that call him Dider
|
2020-11-03 16:09:22 +01:00
|
|
|
|
if "dider" in message.content.lower() and str(message.author.id) not in [constants.myId, constants.didierId, constants.coolerDidierId]:
|
2020-10-13 21:02:40 +02:00
|
|
|
|
await message.add_reaction("<:boos:629603785840263179>")
|
|
|
|
|
|
|
|
|
|
# Check for other easter eggs
|
2020-11-03 16:06:08 +01:00
|
|
|
|
eER = easterEggResponses.control(self.client, message)
|
2020-10-13 21:02:40 +02:00
|
|
|
|
if eER:
|
|
|
|
|
await message.channel.send(eER)
|
|
|
|
|
|
2021-05-17 18:14:33 +02:00
|
|
|
|
# Check for custom commands
|
2021-05-17 20:10:07 +02:00
|
|
|
|
custom = custom_commands.is_custom_command(message)
|
|
|
|
|
|
|
|
|
|
if custom.id is not None:
|
2021-05-17 18:14:33 +02:00
|
|
|
|
await message.channel.send(custom.response)
|
|
|
|
|
|
2020-10-13 21:02:40 +02:00
|
|
|
|
# Earn XP & Message count
|
|
|
|
|
stats.sentMessage(message)
|
|
|
|
|
|
2022-02-10 19:34:40 +01:00
|
|
|
|
@commands.Cog.listener()
|
|
|
|
|
async def on_thread_join(self, thread: discord.Thread):
|
|
|
|
|
# Join threads automatically
|
|
|
|
|
if thread.me is None:
|
|
|
|
|
await thread.join()
|
|
|
|
|
|
2020-11-03 16:09:22 +01:00
|
|
|
|
@commands.Cog.listener()
|
|
|
|
|
async def on_command(self, ctx):
|
|
|
|
|
"""
|
|
|
|
|
Function called whenever someone invokes a command.
|
|
|
|
|
Logs commands in your terminal.
|
|
|
|
|
:param ctx: Discord Context
|
|
|
|
|
"""
|
2021-09-03 20:40:03 +02:00
|
|
|
|
print(stringFormatters.format_command_usage(ctx))
|
2021-06-19 17:45:26 +02:00
|
|
|
|
|
2021-09-03 20:40:03 +02:00
|
|
|
|
command_stats.invoked(command_stats.InvocationType.TextCommand)
|
2020-11-03 16:09:22 +01:00
|
|
|
|
|
2020-10-13 21:02:40 +02:00
|
|
|
|
@commands.Cog.listener()
|
|
|
|
|
async def on_command_error(self, ctx, err):
|
|
|
|
|
"""
|
|
|
|
|
Function called when a command throws an error.
|
|
|
|
|
:param ctx: Discord Context
|
|
|
|
|
:param err: the error thrown
|
|
|
|
|
"""
|
2021-09-03 20:47:25 +02:00
|
|
|
|
# Debugging Didier shouldn't spam the error logs
|
|
|
|
|
if self.client.user.id != int(constants.didierId):
|
2020-12-23 00:18:45 +01:00
|
|
|
|
raise err
|
|
|
|
|
|
2020-10-13 21:02:40 +02:00
|
|
|
|
# Don't handle commands that have their own custom error handler
|
|
|
|
|
if hasattr(ctx.command, 'on_error'):
|
|
|
|
|
return
|
2022-02-10 19:55:04 +01:00
|
|
|
|
|
2020-10-13 21:02:40 +02:00
|
|
|
|
# Someone just mentioned Didier without calling a real command,
|
|
|
|
|
# don't care about this error
|
2020-11-13 10:27:50 +01:00
|
|
|
|
if isinstance(err, (commands.CommandNotFound, commands.CheckFailure, commands.TooManyArguments, commands.ExpectedClosingQuoteError), ):
|
2020-10-13 21:02:40 +02:00
|
|
|
|
pass
|
|
|
|
|
# Someone used a command that was on cooldown
|
|
|
|
|
elif isinstance(err, commands.CommandOnCooldown):
|
|
|
|
|
await ctx.send("Je kan dit commando niet (meer) spammen.", delete_after=10)
|
2021-02-08 20:08:54 +01:00
|
|
|
|
elif isinstance(err, commands.MessageNotFound):
|
|
|
|
|
await ctx.send("Geen message gevonden die overeenkomt met het opgegeven argument.")
|
|
|
|
|
elif isinstance(err, (commands.ChannelNotFound, commands.ChannelNotReadable)):
|
|
|
|
|
await ctx.send("Geen channel gevonden dat overeenkomt met het opgegeven argument.")
|
2022-02-10 19:55:04 +01:00
|
|
|
|
elif isinstance(err, commands.ThreadNotFound):
|
|
|
|
|
await ctx.reply("Thread niet gevonden.", mention_author=False)
|
2020-10-13 21:02:40 +02:00
|
|
|
|
# Someone forgot an argument or passed an invalid argument
|
2021-05-16 12:15:45 +02:00
|
|
|
|
elif isinstance(err, (commands.BadArgument, commands.MissingRequiredArgument, commands.UnexpectedQuoteError)):
|
2022-02-10 19:55:04 +01:00
|
|
|
|
await ctx.reply("Controleer je argumenten.", mention_author=False)
|
2020-10-13 21:02:40 +02:00
|
|
|
|
else:
|
2021-09-03 20:40:03 +02:00
|
|
|
|
usage = stringFormatters.format_command_usage(ctx)
|
|
|
|
|
await self.sendErrorEmbed(err, "Command", usage)
|
|
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
2022-02-05 21:24:08 +01:00
|
|
|
|
async def on_interaction(self, interaction: Interaction):
|
2021-09-03 20:40:03 +02:00
|
|
|
|
"""
|
|
|
|
|
Function called whenever someone uses a slash command
|
|
|
|
|
"""
|
2022-02-05 21:24:08 +01:00
|
|
|
|
if not interaction.is_command():
|
|
|
|
|
return
|
|
|
|
|
|
2021-09-03 20:40:03 +02:00
|
|
|
|
print(stringFormatters.format_slash_command_usage(interaction))
|
|
|
|
|
|
|
|
|
|
command_stats.invoked(command_stats.InvocationType.SlashCommand)
|
|
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
2022-02-05 21:24:08 +01:00
|
|
|
|
async def on_application_command_error(self, ctx: discord.ApplicationContext, err):
|
2021-09-03 20:47:25 +02:00
|
|
|
|
# Debugging Didier shouldn't spam the error logs
|
|
|
|
|
if self.client.user.id != int(constants.didierId):
|
|
|
|
|
raise err
|
|
|
|
|
|
2022-02-05 21:24:08 +01:00
|
|
|
|
if isinstance(err, commands.CheckFailure):
|
|
|
|
|
return await ctx.respond("Je hebt geen toegang tot dit commando.", ephemeral=True)
|
2021-11-29 21:37:04 +01:00
|
|
|
|
|
2022-02-05 21:24:08 +01:00
|
|
|
|
usage = stringFormatters.format_slash_command_usage(ctx.interaction)
|
2021-09-03 20:47:25 +02:00
|
|
|
|
await self.sendErrorEmbed(err, "Slash Command", usage)
|
2020-10-13 21:02:40 +02:00
|
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
|
|
|
|
async def on_raw_reaction_add(self, react):
|
|
|
|
|
"""
|
|
|
|
|
Function called when someone adds a reaction to a message.
|
|
|
|
|
:param react: the RawReactionEvent associated with the reaction
|
|
|
|
|
"""
|
|
|
|
|
# Ignore RPS adding reacts
|
|
|
|
|
if self.client.get_user(react.user_id).bot:
|
|
|
|
|
return
|
|
|
|
|
# Feature request
|
|
|
|
|
if str(react.emoji) == "➕":
|
|
|
|
|
await self.sendReactEmbed(react, "Feature Request")
|
|
|
|
|
# Bug report
|
|
|
|
|
elif str(react.emoji) == "🐛":
|
|
|
|
|
await self.sendReactEmbed(react, "Bug Report")
|
|
|
|
|
# Muttn react
|
|
|
|
|
elif str(react.emoji) == "<:Muttn:761551956346798111>":
|
|
|
|
|
await self.addMuttn(react)
|
|
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
|
|
|
|
async def on_raw_reaction_remove(self, react):
|
|
|
|
|
"""
|
|
|
|
|
Function called when someone removes a reaction from a message.
|
|
|
|
|
:param react: the RawReactionEvent associated with the reaction
|
|
|
|
|
"""
|
|
|
|
|
# Decrease Muttn counter
|
|
|
|
|
if str(react.emoji) == "<:Muttn:761551956346798111>":
|
|
|
|
|
await self.removeMuttn(react)
|
|
|
|
|
|
|
|
|
|
async def removeMuttn(self, react):
|
|
|
|
|
"""
|
|
|
|
|
Function that decreases the Muttn counter for someone.
|
|
|
|
|
:param react: the RawReactionEvent associated with the reaction
|
|
|
|
|
"""
|
|
|
|
|
# Get the Message instance of the message
|
|
|
|
|
channel = self.client.get_channel(react.channel_id)
|
|
|
|
|
message = await channel.fetch_message(react.message_id)
|
|
|
|
|
muttn.removeMuttn(message)
|
|
|
|
|
|
|
|
|
|
async def addMuttn(self, react):
|
|
|
|
|
"""
|
|
|
|
|
Function that checks the Muttn counter for a message.
|
|
|
|
|
:param react: the RawReactionEvent associated with the reaction
|
|
|
|
|
"""
|
|
|
|
|
count = -1
|
|
|
|
|
# Get the Message instance of the message
|
|
|
|
|
channel = self.client.get_channel(react.channel_id)
|
|
|
|
|
message = await channel.fetch_message(react.message_id)
|
|
|
|
|
|
|
|
|
|
# Get the amount of reacts on this message
|
|
|
|
|
for reaction in message.reactions:
|
|
|
|
|
if str(reaction.emoji) == "<:Muttn:761551956346798111>":
|
|
|
|
|
count = reaction.count
|
|
|
|
|
for user in await reaction.users().flatten():
|
|
|
|
|
# Remove bot reacts
|
|
|
|
|
if user.bot:
|
|
|
|
|
count -= 1
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
# React was removed in the milliseconds the fetch_message needs to get the info
|
|
|
|
|
if count <= 0:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Update the db
|
|
|
|
|
muttn.muttn(message.author.id, count, message.id)
|
|
|
|
|
|
|
|
|
|
def reactCheck(self, react, msg):
|
|
|
|
|
"""
|
|
|
|
|
Function that checks if feature requests/bug reports have been sent already.
|
|
|
|
|
:param react: the RawReactionEvent associated with the reaction
|
|
|
|
|
:param msg: the message this react was placed on
|
|
|
|
|
"""
|
2020-11-15 11:20:40 +01:00
|
|
|
|
# # Blacklist NinjaJay after spamming
|
|
|
|
|
# if react.user_id in [153162010576551946]:
|
|
|
|
|
# return False
|
2020-10-13 21:02:40 +02:00
|
|
|
|
|
|
|
|
|
# Don't spam DM's when something has already been reported
|
|
|
|
|
# Check if the react's count is 1
|
|
|
|
|
for reaction in msg.reactions:
|
|
|
|
|
if reaction.emoji == react.emoji.name:
|
|
|
|
|
return reaction.count == 1
|
|
|
|
|
|
|
|
|
|
async def sendReactEmbed(self, react, messageType):
|
|
|
|
|
"""
|
|
|
|
|
Function that sends a message in Zandbak with what's going on.
|
|
|
|
|
:param react: the RawReactionEvent associated with the reaction
|
|
|
|
|
:param messageType: the type of message to send
|
|
|
|
|
"""
|
|
|
|
|
channel = self.client.get_channel(react.channel_id)
|
|
|
|
|
msg = await channel.fetch_message(react.message_id)
|
|
|
|
|
|
|
|
|
|
# Didn't pass the check, ignore it
|
|
|
|
|
if not self.reactCheck(react, msg):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
typeChannels = {"Feature Request": int(constants.FeatureRequests), "Bug Report": int(constants.BugReports)}
|
|
|
|
|
|
|
|
|
|
# Add a 10 second cooldown to requests/reports to avoid spam
|
|
|
|
|
# even tho apparently the people don't care
|
|
|
|
|
if round(time.time()) - (
|
|
|
|
|
self.lastFeatureRequest if messageType == "Feature Request" else self.lastBugReport) < 10:
|
|
|
|
|
await channel.send("Je moet even wachten vooraleer je nog een {} maakt.".format(messageType.lower()))
|
|
|
|
|
await msg.add_reaction("🕐")
|
|
|
|
|
return
|
|
|
|
|
# Report on an empty message
|
|
|
|
|
elif msg.content == "":
|
|
|
|
|
await channel.send("Dit bericht bevat geen tekst.")
|
|
|
|
|
await msg.add_reaction("❌")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Update the variables
|
|
|
|
|
if messageType == "Feature Request":
|
|
|
|
|
self.lastFeatureRequest = round(time.time())
|
|
|
|
|
else:
|
|
|
|
|
self.lastBugReport = round(time.time())
|
|
|
|
|
|
|
|
|
|
# Ignore people reacting on Didier's messages
|
|
|
|
|
if str(msg.author.id) != constants.didierId:
|
|
|
|
|
# Get the user's User instance & the channel to send the message to
|
|
|
|
|
COC = self.client.get_guild(int(constants.CallOfCode))
|
|
|
|
|
user = COC.get_member(react.user_id)
|
|
|
|
|
targetChannel = self.client.get_channel(typeChannels[messageType])
|
|
|
|
|
|
|
|
|
|
await targetChannel.send("{} door **{}** in **#{}** ({}):\n``{}``\n{}".format(
|
|
|
|
|
messageType,
|
|
|
|
|
user.display_name,
|
|
|
|
|
channel.name if str(channel.type) != "private" else "DM",
|
|
|
|
|
channel.guild.name if str(channel.type) != "private" else "DM",
|
|
|
|
|
msg.content, msg.jump_url
|
|
|
|
|
))
|
|
|
|
|
await msg.add_reaction("✅")
|
|
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
2021-06-22 00:36:22 +02:00
|
|
|
|
async def on_message_edit(self, before: discord.Message, after: discord.Message):
|
2020-10-13 21:02:40 +02:00
|
|
|
|
"""
|
|
|
|
|
Function called when a message is edited,
|
|
|
|
|
so people can't edit messages in FreeGames to cheat the system.
|
|
|
|
|
:param before: the message before it was edited
|
|
|
|
|
:param after: the message after it was edited
|
|
|
|
|
"""
|
|
|
|
|
# Run the message through the checks again
|
|
|
|
|
if not checks.freeGamesCheck(after):
|
2021-06-22 00:36:22 +02:00
|
|
|
|
return await self.failedChecksCog.freeGames(after)
|
|
|
|
|
|
2021-06-30 21:55:17 +02:00
|
|
|
|
if should_snipe(before):
|
2021-06-22 00:36:22 +02:00
|
|
|
|
self.client.snipe[before.channel.id] = Snipe(before.author.id, before.channel.id, before.guild.id, Action.Edit,
|
|
|
|
|
before.content, after.content)
|
|
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
|
|
|
|
async def on_message_delete(self, message: discord.Message):
|
2021-06-30 21:55:17 +02:00
|
|
|
|
if should_snipe(message):
|
2021-06-22 00:36:22 +02:00
|
|
|
|
self.client.snipe[message.channel.id] = Snipe(message.author.id, message.channel.id, message.guild.id,
|
|
|
|
|
Action.Remove, message.content)
|
2020-10-13 21:02:40 +02:00
|
|
|
|
|
2021-09-03 20:40:03 +02:00
|
|
|
|
async def sendErrorEmbed(self, error: Exception, error_type: str, usage: str):
|
2020-10-13 21:02:40 +02:00
|
|
|
|
"""
|
|
|
|
|
Function that sends an error embed in #ErrorLogs.
|
|
|
|
|
"""
|
2021-09-03 20:40:03 +02:00
|
|
|
|
trace = stringFormatters.format_error_tb(error)
|
|
|
|
|
|
2020-10-13 21:02:40 +02:00
|
|
|
|
embed = discord.Embed(colour=discord.Colour.red())
|
2021-09-03 20:47:25 +02:00
|
|
|
|
embed.set_author(name="Error")
|
|
|
|
|
embed.add_field(name=f"{error_type}:", value=usage, inline=False)
|
2020-10-13 21:02:40 +02:00
|
|
|
|
embed.add_field(name="Error:", value=str(error)[:1024], inline=False)
|
|
|
|
|
embed.add_field(name="Message:", value=str(trace)[:1024], inline=False)
|
|
|
|
|
|
|
|
|
|
# Add remaining parts in extra fields
|
|
|
|
|
# (embed field limits)
|
|
|
|
|
if len(str(trace)) < 5500:
|
|
|
|
|
trace_split = [str(trace)[i:i + 1024] for i in range(1024, len(str(trace)), 1024)]
|
|
|
|
|
for spl in trace_split:
|
|
|
|
|
embed.add_field(name="\u200b", value=spl, inline=False)
|
|
|
|
|
|
|
|
|
|
errorChannel = self.client.get_channel(762668505455132722)
|
|
|
|
|
await errorChannel.send(embed=embed)
|
|
|
|
|
|
|
|
|
|
@commands.command(hidden=True)
|
|
|
|
|
@commands.check(checks.isMe)
|
|
|
|
|
async def lock(self, ctx, until=None):
|
|
|
|
|
"""
|
|
|
|
|
Command that locks the server during online exams.
|
|
|
|
|
:param ctx: Discord Context
|
|
|
|
|
:param until: the timestamp until which to lock (HH:MM)
|
|
|
|
|
"""
|
|
|
|
|
# No timestamp passed
|
|
|
|
|
if until is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
until = until.split(":")
|
|
|
|
|
|
|
|
|
|
# Create timestamps
|
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
|
untilTimestamp = time.time()
|
|
|
|
|
|
|
|
|
|
# Gets the current amount of minutes into the day
|
|
|
|
|
nowMinuteCount = (now.hour * 60) + now.minute
|
|
|
|
|
|
|
|
|
|
# Gets the target amount of minutes into the day
|
|
|
|
|
untilMinuteCount = (int(until[0]) * 60) + int(until[1])
|
|
|
|
|
|
|
|
|
|
# Adds the remaining seconds onto the current time to calculate the end of the lock
|
|
|
|
|
untilTimestamp += (60 * (untilMinuteCount - nowMinuteCount)) - now.second
|
|
|
|
|
|
|
|
|
|
self.client.locked = True
|
|
|
|
|
self.client.lockedUntil = round(untilTimestamp)
|
|
|
|
|
|
|
|
|
|
await ctx.send("De server wordt gelocked tot **{}**.".format(
|
|
|
|
|
datetime.datetime.fromtimestamp(untilTimestamp,
|
|
|
|
|
pytz.timezone("Europe/Brussels")
|
|
|
|
|
).strftime('%H:%M:%S')))
|
|
|
|
|
|
|
|
|
|
@commands.command(hidden=True)
|
|
|
|
|
@commands.check(checks.isMe)
|
|
|
|
|
async def unlock(self, ctx):
|
|
|
|
|
"""
|
|
|
|
|
Command to unlock the server manually before the timer is over.
|
|
|
|
|
:param ctx: Discord Context
|
|
|
|
|
"""
|
|
|
|
|
self.client.locked = False
|
|
|
|
|
self.client.lockedUntil = -1
|
|
|
|
|
await ctx.send("De server is niet langer gelocked.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def setup(client):
|
|
|
|
|
client.add_cog(Events(client))
|