Compare commits

...

3 Commits

16 changed files with 188 additions and 94 deletions

View File

@ -1,9 +1,11 @@
from dislash import SlashInteraction
from data import constants
from data.snipe import Snipe, Action, should_snipe
import datetime
import discord
from discord.ext import commands
from functions import checks, easterEggResponses
from functions import checks, easterEggResponses, stringFormatters
from functions.database import stats, muttn, custom_commands, commands as command_stats
import pytz
from settings import READY_MESSAGE, SANDBOX, STATUS_MESSAGE
@ -87,12 +89,9 @@ class Events(commands.Cog):
Logs commands in your terminal.
:param ctx: Discord Context
"""
DM = ctx.guild is None
print("{} in {}: {}".format(ctx.author.display_name,
"DM" if DM else "{} ({})".format(ctx.channel.name, ctx.guild.name),
ctx.message.content))
print(stringFormatters.format_command_usage(ctx))
command_stats.invoked()
command_stats.invoked(command_stats.InvocationType.TextCommand)
@commands.Cog.listener()
async def on_command_error(self, ctx, err):
@ -101,8 +100,8 @@ class Events(commands.Cog):
:param ctx: Discord Context
:param err: the error thrown
"""
# Zandbak Didier shouldn't spam the error logs
if self.client.user.id == int(constants.coolerDidierId):
# Debugging Didier shouldn't spam the error logs
if self.client.user.id != int(constants.didierId):
raise err
# Don't handle commands that have their own custom error handler
@ -123,14 +122,26 @@ class Events(commands.Cog):
elif isinstance(err, (commands.BadArgument, commands.MissingRequiredArgument, commands.UnexpectedQuoteError)):
await ctx.send("Controleer je argumenten.")
else:
# Remove the InvokeCommandError because it's useless information
x = traceback.format_exception(type(err), err, err.__traceback__)
errorString = ""
for line in x:
if "direct cause of the following" in line:
break
errorString += line.replace("*", "") + "\n" if line.strip() != "" else ""
await self.sendErrorEmbed(ctx, err, errorString)
usage = stringFormatters.format_command_usage(ctx)
await self.sendErrorEmbed(err, "Command", usage)
@commands.Cog.listener()
async def on_slash_command(self, interaction: SlashInteraction):
"""
Function called whenever someone uses a slash command
"""
print(stringFormatters.format_slash_command_usage(interaction))
command_stats.invoked(command_stats.InvocationType.SlashCommand)
@commands.Cog.listener()
async def on_slash_command_error(self, interaction, err):
# Debugging Didier shouldn't spam the error logs
if self.client.user.id != int(constants.didierId):
raise err
usage = stringFormatters.format_slash_command_usage(interaction)
await self.sendErrorEmbed(err, "Slash Command", usage)
@commands.Cog.listener()
async def on_raw_reaction_add(self, react):
@ -286,19 +297,15 @@ class Events(commands.Cog):
self.client.snipe[message.channel.id] = Snipe(message.author.id, message.channel.id, message.guild.id,
Action.Remove, message.content)
async def sendErrorEmbed(self, ctx, error: Exception, trace):
async def sendErrorEmbed(self, error: Exception, error_type: str, usage: str):
"""
Function that sends an error embed in #ErrorLogs.
:param ctx: Discord Context
:param error: the error thrown
:param trace: the stacktrace of the error
"""
trace = stringFormatters.format_error_tb(error)
embed = discord.Embed(colour=discord.Colour.red())
embed.set_author(name="Error")
embed.add_field(name="Command:", value="{} in {}: {}".format(ctx.author.display_name,
ctx.channel.name if str(
ctx.channel.type) != "private" else "DM",
ctx.message.content), inline=False)
embed.add_field(name=f"{error_type}:", value=usage, inline=False)
embed.add_field(name="Error:", value=str(error)[:1024], inline=False)
embed.add_field(name="Message:", value=str(trace)[:1024], inline=False)

View File

@ -2,7 +2,7 @@ from decorators import help
from discord.ext import commands
from enums.help_categories import Category
from functions import checks, config
from functions.football import getMatches, getTable, get_jpl_code
from functions.football import get_matches, get_table, get_jpl_code
class Football(commands.Cog):
@ -20,21 +20,16 @@ class Football(commands.Cog):
pass
@jpl.command(name="Matches", aliases=["M"], usage="[Week]*")
async def matches(self, ctx, *args):
args = list(args)
async def matches(self, ctx, day: int = None):
# Default is current day
if not args:
args = [str(config.get("jpl_day"))]
if day is None:
day = int(config.get("jpl_day"))
if all(letter.isdigit() for letter in args[0]):
await ctx.send(getMatches(int(args[0])))
else:
return await ctx.send("Dit is geen geldige speeldag.")
await ctx.send(get_matches(day))
@jpl.command(name="Table", aliases=["Ranking", "Rankings", "Ranks", "T"])
async def table(self, ctx, *args):
await ctx.send(getTable())
async def table(self, ctx):
await ctx.send(get_table())
@commands.check(checks.isMe)
@jpl.command(name="Update")

View File

@ -1,4 +1,3 @@
import discord
from discord.ext import commands
from decorators import help
from enums.help_categories import Category

View File

@ -42,7 +42,7 @@ class School(commands.Cog):
embed.set_footer(text="Omwille van de coronamaatregelen is er een beperkter aanbod, en kan je enkel nog eten afhalen. Ter plaatse eten is niet meer mogelijk.")
await ctx.send(embed=embed)
# @commands.command(name="Les", aliases=["Class", "Classes", "Sched", "Schedule"], usage="[Jaargang]* [Dag]*")
# @commands.command(name="Les", aliases=["Class", "Classes", "Sched", "Schedule"], usage="[Dag]*")
# @commands.check(checks.allowedChannels)
# @help.Category(category=Category.School)
async def les(self, ctx, day=None):

View File

@ -13,8 +13,7 @@ class DefineSlash(commands.Cog):
description="Urban Dictionary",
options=[
Option("query", "Search query", OptionType.STRING, required=True)
],
guild_ids=[728361030404538488, 880175869841277008]
]
)
async def _define_slash(self, interaction: SlashInteraction, query):
embed = Definition(query).to_embed()

View File

@ -0,0 +1,44 @@
from discord.ext import commands
from dislash import SlashInteraction, slash_command, Option, OptionType
from functions import config, checks
from functions.football import get_matches, get_table, get_jpl_code
from startup.didier import Didier
class FootballSlash(commands.Cog):
def __init__(self, client: Didier):
self.client: Didier = client
@slash_command(name="jpl", description="Jupiler Pro League commands")
async def _jpl_group(self, interaction: SlashInteraction):
pass
@_jpl_group.sub_command(name="matches",
description="Schema voor een bepaalde speeldag",
options=[
Option("day", "Speeldag (default huidige)", OptionType.INTEGER)
]
)
async def _jpl_matches_slash(self, interaction: SlashInteraction, day: int = None):
# Default is current day
if day is None:
day = int(config.get("jpl_day"))
await interaction.reply(get_matches(day))
@_jpl_group.sub_command(name="table", description="Huidige rangschikking")
async def _jpl_table_slash(self, interaction: SlashInteraction):
await interaction.reply(get_table())
@_jpl_group.sub_command(name="update", description="Update de code voor deze competitie (owner-only)")
async def _jpl_update_slash(self, interaction: SlashInteraction):
if not await checks.isMe(interaction):
return await interaction.reply(f"Je hebt geen toegang tot dit commando.")
code = get_jpl_code()
config.config("jpl", code)
await interaction.reply(f"Done (code: {code})")
def setup(client: Didier):
client.add_cog(FootballSlash(client))

View File

@ -12,8 +12,7 @@ class GoogleSlash(commands.Cog):
description="Google search",
options=[
Option("query", "Search query", OptionType.STRING, required=True)
],
guild_ids=[728361030404538488, 880175869841277008]
]
)
async def _google_slash(self, interaction: SlashInteraction, query: str):
result = google_search(query)

View File

@ -14,11 +14,12 @@ class TranslateSlash(commands.Cog):
description="Google Translate",
options=[
Option("text", "Tekst om te vertalen", OptionType.STRING, required=True),
Option("to", "Taal om naar te vertalen (default NL)", OptionType.STRING)
Option("from_lang", "Taal om van te vertalen (default auto-detect)", OptionType.STRING),
Option("to_lang", "Taal om naar te vertalen (default NL)", OptionType.STRING)
]
)
async def _translate_slash(self, interaction: SlashInteraction, text: str, to: str = "nl"):
translation = Translation(text=text, to=to.lower())
async def _translate_slash(self, interaction: SlashInteraction, text: str, from_lang: str = "auto", to_lang: str = "nl"):
translation = Translation(text=text, fr=from_lang.lower(), to=to_lang.lower())
await interaction.reply(embed=translation.to_embed())

View File

@ -5,26 +5,32 @@ from typing import Optional
class Translation:
def __init__(self, text: str, to: str):
def __init__(self, text: str, fr: str, to: str):
self.text = text
self.fr = fr
self.to = to
self.embed: Optional[discord.Embed] = None
self.translation = None
self.translate(text, to)
self.translate(text, fr, to)
def translate(self, query: str, to: str):
def translate(self, query: str, fr: str, to: str):
"""
Translate [query] into [to]
"""
try:
translator = Translator()
self.translation = translator.translate(query, to, "auto")
self.translation = translator.translate(query, to, fr)
except ValueError as e:
message = str(e)
if "destination" in message:
self._create_error_embed(f"{title_case(to)} is geen geldige taal.")
return
if "source" in message:
self._create_error_embed(f"{title_case(fr)} is geen geldige taal.")
return
raise e
@ -42,8 +48,9 @@ class Translation:
embed = discord.Embed(colour=discord.Colour.blue())
embed.set_author(name="Didier Translate")
language = self.translation.src
embed.add_field(name="Gedetecteerde taal", value=title_case(LANGUAGES[language]))
if self.fr == "auto":
language = self.translation.src
embed.add_field(name="Gedetecteerde taal", value=title_case(LANGUAGES[language]))
if self.translation.extra_data["confidence"] is not None:
embed.add_field(name="Zekerheid", value="{}%".format(self.translation.extra_data["confidence"] * 100))

View File

@ -58,7 +58,7 @@
"jpl table": "De huidige stand van het klassement.",
"jpl update": "Haalt de nieuwe code voor de competitie van dit jaar op.",
"leaderboard": "Bekijk de Top 10 van [Categorie].\nIndien je geen categorie opgeeft krijg je een lijst van categorieën.",
"les": "Bekijk het lessenrooster voor [Dag] in het [Jaargang]-de jaar.\nIndien je geen dag opgeeft, is dit standaard vandaag. De jaargang is standaard 2.\nLes Morgen/Overmorgen werkt ook.",
"les": "Bekijk het lessenrooster voor [Dag].\nIndien je geen dag opgeeft, is dit standaard vandaag.\nLes Morgen/Overmorgen werkt ook.",
"lmgtfy": "Stuur iemand een LMGTFY link wanneer ze je een domme vraag stellen in plaats van het zelf op te zoeken.\nQueries met spaties moeten **niet** tussen aanhalingstekens staan.",
"load": "Laadt [Cog] in.",
"load all": "Laadt alle cogs in.",

View File

@ -1,8 +1,4 @@
import math
import discord
from discord import utils, Member, User
from discord.ext import commands
from data import constants
import requests
from functions.database import currency

View File

@ -1,23 +1,20 @@
from enum import IntEnum
from functions.database import utils
from functions.stringFormatters import leading_zero as lz
import time
def invoked():
class InvocationType(IntEnum):
TextCommand = 0
SlashCommand = 1
ContextMenu = 2
def invoked(inv: InvocationType):
t = time.localtime()
day_string: str = f"{t.tm_year}-{_lz(t.tm_mon)}-{_lz(t.tm_mday)}"
_update(day_string)
def _lz(arg: int) -> str:
"""
Add leading zeroes if necessary (YYYY-MM-DD)
"""
arg = str(arg)
if len(arg) == 1:
return f"0{arg}"
return arg
day_string: str = f"{t.tm_year}-{lz(t.tm_mon)}-{lz(t.tm_mday)}"
_update(day_string, inv)
def _is_present(date: str) -> bool:
@ -43,25 +40,27 @@ def _add_date(date: str):
connection = utils.connect()
cursor = connection.cursor()
cursor.execute("INSERT INTO command_stats(day, amount) VALUES (%s, 1)", (date,))
cursor.execute("INSERT INTO command_stats(day, commands, slash_commands, context_menus) VALUES (%s, 0, 0, 0)", (date,))
connection.commit()
def _update(date: str):
def _update(date: str, inv: InvocationType):
"""
Increase the counter for a given day
"""
# Date wasn't present yet, add it with a value of 1
# Date wasn't present yet, add it
if not _is_present(date):
_add_date(date)
return
connection = utils.connect()
cursor = connection.cursor()
cursor.execute("""
column_name = ["commands", "slash_commands", "context_menus"][inv.value]
# String formatting is safe here because the input comes from above ^
cursor.execute(f"""
UPDATE command_stats
SET amount = amount + 1
SET {column_name} = {column_name} + 1
WHERE day = %s
""", (date,))
connection.commit()

View File

@ -39,11 +39,11 @@ class Match:
Parse class attributes out of a dictionary returned from an API request
"""
# The API isn't public, so every single game state is differently formatted
self.status = self._getStatus(self.matchDict[Navigation.Status.value])
self.status = self._get_status(self.matchDict[Navigation.Status.value])
self.home = self.matchDict[Navigation.HomeTeam.value][Navigation.Name.value]
self.away = self.matchDict[Navigation.AwayTeam.value][Navigation.Name.value]
if self._hasStarted():
if self._has_started():
self.homeScore = self.matchDict[Navigation.HomeScore.value]
self.awayScore = self.matchDict[Navigation.AwayScore.value]
@ -53,9 +53,9 @@ class Match:
self.start = None
self.date = self.start.strftime("%d/%m") if self.start is not None else "Uitgesteld"
self.weekDay = self._getWeekday() if self.start is not None else "??"
self.weekDay = self._get_weekday() if self.start is not None else "??"
def _getStatus(self, status: str):
def _get_status(self, status: str):
"""
Gets the string representation for the status of this match
"""
@ -80,7 +80,7 @@ class Match:
return statusses[status.lower()]
def _getWeekday(self):
def _get_weekday(self):
"""
Gets the day of the week this match is played on
"""
@ -88,13 +88,13 @@ class Match:
days = ["Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"]
return days[day]
def getInfo(self):
def get_info(self):
"""
Returns a list of all the info of this class in order to create a table
"""
return [self.weekDay, self.date, self.home, self._getScore(), self.away, self.status]
return [self.weekDay, self.date, self.home, self._get_score(), self.away, self.status]
def _getScore(self):
def _get_score(self):
"""
Returns a string representing the scoreboard
"""
@ -102,12 +102,12 @@ class Match:
return "??"
# No score to show yet, show time when the match starts
if not self._hasStarted():
if not self._has_started():
return "{}:{}".format(leading_zero(str(self.start.hour)), leading_zero(str(self.start.minute)))
return "{} - {}".format(self.homeScore, self.awayScore)
def _hasStarted(self):
def _has_started(self):
return self.status not in [Status.AfterToday.value, Status.NotStarted.value, Status.Postponed.value]
@ -128,7 +128,7 @@ class Navigation(Enum):
Name = "name"
def getMatches(matchweek: int):
def get_matches(matchweek: int):
"""
Function that constructs the list of matches for a given matchweek
"""
@ -139,7 +139,7 @@ def getMatches(matchweek: int):
return "Er ging iets fout. Probeer het later opnieuw."
matches = list(map(Match, current_day))
matches = list(map(lambda x: x.getInfo(), matches))
matches = list(map(lambda x: x.get_info(), matches))
header = "Jupiler Pro League - Speeldag {}".format(matchweek)
table = tabulate.tabulate(matches, headers=["Dag", "Datum", "Thuis", "Stand", "Uit", "Tijd"])
@ -147,7 +147,7 @@ def getMatches(matchweek: int):
return "```{}\n\n{}```".format(header, table)
def getTable():
def get_table():
"""
Function that constructs the current table of the JPL
"""
@ -157,7 +157,7 @@ def getTable():
return "Er ging iets fout. Probeer het later opnieuw."
# Format every row to work for Tabulate
formatted = [_formatRow(row) for row in rows]
formatted = [_format_row(row) for row in rows]
header = "Jupiler Pro League Klassement"
table = tabulate.tabulate(formatted, headers=["#", "Ploeg", "Punten", "M", "M+", "M-", "M=", "D+", "D-", "D+/-"])
@ -165,7 +165,7 @@ def getTable():
return "```{}\n\n{}```".format(header, table)
def _formatRow(row):
def _format_row(row):
"""
Function that formats a row into a list for Tabulate to use
"""

View File

@ -1,3 +1,9 @@
import traceback
from discord.ext.commands import Context
from dislash import SlashInteraction
def title_case(string):
return " ".join(capitalize(word) for word in string.split(" "))
@ -13,3 +19,35 @@ def leading_zero(string, size=2):
while len(string) < size:
string = "0" + string
return string
def format_error_tb(err: Exception) -> str:
# Remove the InvokeCommandError because it's useless information
x = traceback.format_exception(type(err), err, err.__traceback__)
error_string = ""
for line in x:
if "direct cause of the following" in line:
break
error_string += line.replace("*", "") + "\n" if line.strip() != "" else ""
return error_string
def _format_error_location(src) -> str:
DM = src.guild is None
return "DM" if DM else f"{src.channel.name} ({src.guild.name})"
def format_command_usage(ctx: Context) -> str:
return f"{ctx.author.display_name} in {_format_error_location(ctx)}: {ctx.message.content}"
def format_slash_command_usage(interaction: SlashInteraction) -> str:
# Create a string with the options used
options = " ".join(list(map(
lambda option: f"{option.name}: \"{option.value}\"",
interaction.data.options.values()
)))
command = f"{interaction.slash_command.name} {options or ''}"
return f"{interaction.author.display_name} in {_format_error_location(interaction)}: /{command}"

View File

@ -1,3 +1,5 @@
from typing import List
from dotenv import load_dotenv
import os
@ -31,3 +33,11 @@ TOKEN = os.getenv("TOKEN", "")
HOST_IPC = _to_bool(os.getenv("HOSTIPC", "false"))
READY_MESSAGE = os.getenv("READYMESSAGE", "I'M READY I'M READY I'M READY I'M READY") # Yes, this is a Spongebob reference
STATUS_MESSAGE = os.getenv("STATUSMESSAGE", "with your Didier Dinks.")
# Guilds to test slash commands in
# Ex: 123,456,789
SLASH_TEST_GUILDS: List[int] = list(
map(lambda x: int(x),
os.getenv("SLASHTESTGUILDS", "").replace(" ", "").split(",")
)
)

View File

@ -2,7 +2,7 @@ from data.snipe import Snipe
from discord.ext import commands, ipc
from dislash import InteractionClient
import os
from settings import HOST_IPC
from settings import HOST_IPC, SLASH_TEST_GUILDS
from startup.init_files import check_all
from typing import Dict
@ -33,7 +33,7 @@ class Didier(commands.Bot):
self.remove_command("help")
# Create interactions client
self.interactions = InteractionClient(self, test_guilds=[728361030404538488, 880175869841277008])
self.interactions = InteractionClient(self, test_guilds=SLASH_TEST_GUILDS)
# Load all extensions
self.init_extensions()