mirror of https://github.com/stijndcl/didier
Enable commands globally if not sandboxing, add support for test guilds in env, monitor slash command & context menu usage, create error handler for slash commands, log slash commands in terminal
parent
ef547a7090
commit
a28bd116f0
|
@ -1,9 +1,11 @@
|
||||||
|
from dislash import SlashInteraction
|
||||||
|
|
||||||
from data import constants
|
from data import constants
|
||||||
from data.snipe import Snipe, Action, should_snipe
|
from data.snipe import Snipe, Action, should_snipe
|
||||||
import datetime
|
import datetime
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
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
|
from functions.database import stats, muttn, custom_commands, commands as command_stats
|
||||||
import pytz
|
import pytz
|
||||||
from settings import READY_MESSAGE, SANDBOX, STATUS_MESSAGE
|
from settings import READY_MESSAGE, SANDBOX, STATUS_MESSAGE
|
||||||
|
@ -87,12 +89,9 @@ class Events(commands.Cog):
|
||||||
Logs commands in your terminal.
|
Logs commands in your terminal.
|
||||||
:param ctx: Discord Context
|
:param ctx: Discord Context
|
||||||
"""
|
"""
|
||||||
DM = ctx.guild is None
|
print(stringFormatters.format_command_usage(ctx))
|
||||||
print("{} in {}: {}".format(ctx.author.display_name,
|
|
||||||
"DM" if DM else "{} ({})".format(ctx.channel.name, ctx.guild.name),
|
|
||||||
ctx.message.content))
|
|
||||||
|
|
||||||
command_stats.invoked()
|
command_stats.invoked(command_stats.InvocationType.TextCommand)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_command_error(self, ctx, err):
|
async def on_command_error(self, ctx, err):
|
||||||
|
@ -123,14 +122,22 @@ class Events(commands.Cog):
|
||||||
elif isinstance(err, (commands.BadArgument, commands.MissingRequiredArgument, commands.UnexpectedQuoteError)):
|
elif isinstance(err, (commands.BadArgument, commands.MissingRequiredArgument, commands.UnexpectedQuoteError)):
|
||||||
await ctx.send("Controleer je argumenten.")
|
await ctx.send("Controleer je argumenten.")
|
||||||
else:
|
else:
|
||||||
# Remove the InvokeCommandError because it's useless information
|
usage = stringFormatters.format_command_usage(ctx)
|
||||||
x = traceback.format_exception(type(err), err, err.__traceback__)
|
await self.sendErrorEmbed(err, "Command", usage)
|
||||||
errorString = ""
|
|
||||||
for line in x:
|
@commands.Cog.listener()
|
||||||
if "direct cause of the following" in line:
|
async def on_slash_command(self, interaction: SlashInteraction):
|
||||||
break
|
"""
|
||||||
errorString += line.replace("*", "") + "\n" if line.strip() != "" else ""
|
Function called whenever someone uses a slash command
|
||||||
await self.sendErrorEmbed(ctx, err, errorString)
|
"""
|
||||||
|
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, error):
|
||||||
|
usage = stringFormatters.format_slash_command_usage(interaction)
|
||||||
|
await self.sendErrorEmbed(error, "Slash Command", usage)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_raw_reaction_add(self, react):
|
async def on_raw_reaction_add(self, react):
|
||||||
|
@ -286,19 +293,15 @@ class Events(commands.Cog):
|
||||||
self.client.snipe[message.channel.id] = Snipe(message.author.id, message.channel.id, message.guild.id,
|
self.client.snipe[message.channel.id] = Snipe(message.author.id, message.channel.id, message.guild.id,
|
||||||
Action.Remove, message.content)
|
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.
|
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 = discord.Embed(colour=discord.Colour.red())
|
||||||
embed.set_author(name="Error")
|
embed.set_author(name=f"{error_type} Error")
|
||||||
embed.add_field(name="Command:", value="{} in {}: {}".format(ctx.author.display_name,
|
embed.add_field(name="Command:", value=usage, inline=False)
|
||||||
ctx.channel.name if str(
|
|
||||||
ctx.channel.type) != "private" else "DM",
|
|
||||||
ctx.message.content), inline=False)
|
|
||||||
embed.add_field(name="Error:", value=str(error)[:1024], inline=False)
|
embed.add_field(name="Error:", value=str(error)[:1024], inline=False)
|
||||||
embed.add_field(name="Message:", value=str(trace)[:1024], inline=False)
|
embed.add_field(name="Message:", value=str(trace)[:1024], inline=False)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import discord
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from decorators import help
|
from decorators import help
|
||||||
from enums.help_categories import Category
|
from enums.help_categories import Category
|
||||||
|
|
|
@ -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.")
|
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)
|
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)
|
# @commands.check(checks.allowedChannels)
|
||||||
# @help.Category(category=Category.School)
|
# @help.Category(category=Category.School)
|
||||||
async def les(self, ctx, day=None):
|
async def les(self, ctx, day=None):
|
||||||
|
|
|
@ -13,8 +13,7 @@ class DefineSlash(commands.Cog):
|
||||||
description="Urban Dictionary",
|
description="Urban Dictionary",
|
||||||
options=[
|
options=[
|
||||||
Option("query", "Search query", OptionType.STRING, required=True)
|
Option("query", "Search query", OptionType.STRING, required=True)
|
||||||
],
|
]
|
||||||
guild_ids=[728361030404538488, 880175869841277008]
|
|
||||||
)
|
)
|
||||||
async def _define_slash(self, interaction: SlashInteraction, query):
|
async def _define_slash(self, interaction: SlashInteraction, query):
|
||||||
embed = Definition(query).to_embed()
|
embed = Definition(query).to_embed()
|
||||||
|
|
|
@ -12,8 +12,7 @@ class GoogleSlash(commands.Cog):
|
||||||
description="Google search",
|
description="Google search",
|
||||||
options=[
|
options=[
|
||||||
Option("query", "Search query", OptionType.STRING, required=True)
|
Option("query", "Search query", OptionType.STRING, required=True)
|
||||||
],
|
]
|
||||||
guild_ids=[728361030404538488, 880175869841277008]
|
|
||||||
)
|
)
|
||||||
async def _google_slash(self, interaction: SlashInteraction, query: str):
|
async def _google_slash(self, interaction: SlashInteraction, query: str):
|
||||||
result = google_search(query)
|
result = google_search(query)
|
||||||
|
|
|
@ -14,11 +14,12 @@ class TranslateSlash(commands.Cog):
|
||||||
description="Google Translate",
|
description="Google Translate",
|
||||||
options=[
|
options=[
|
||||||
Option("text", "Tekst om te vertalen", OptionType.STRING, required=True),
|
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"):
|
async def _translate_slash(self, interaction: SlashInteraction, text: str, from_lang: str = "auto", to_lang: str = "nl"):
|
||||||
translation = Translation(text=text, to=to.lower())
|
translation = Translation(text=text, fr=from_lang.lower(), to=to_lang.lower())
|
||||||
await interaction.reply(embed=translation.to_embed())
|
await interaction.reply(embed=translation.to_embed())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,26 +5,32 @@ from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class Translation:
|
class Translation:
|
||||||
def __init__(self, text: str, to: str):
|
def __init__(self, text: str, fr: str, to: str):
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.fr = fr
|
||||||
self.to = to
|
self.to = to
|
||||||
self.embed: Optional[discord.Embed] = None
|
self.embed: Optional[discord.Embed] = None
|
||||||
self.translation = 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]
|
Translate [query] into [to]
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
translator = Translator()
|
translator = Translator()
|
||||||
self.translation = translator.translate(query, to, "auto")
|
self.translation = translator.translate(query, to, fr)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
message = str(e)
|
message = str(e)
|
||||||
|
|
||||||
if "destination" in message:
|
if "destination" in message:
|
||||||
self._create_error_embed(f"{title_case(to)} is geen geldige taal.")
|
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
|
raise e
|
||||||
|
|
||||||
|
@ -42,6 +48,7 @@ class Translation:
|
||||||
embed = discord.Embed(colour=discord.Colour.blue())
|
embed = discord.Embed(colour=discord.Colour.blue())
|
||||||
embed.set_author(name="Didier Translate")
|
embed.set_author(name="Didier Translate")
|
||||||
|
|
||||||
|
if self.fr == "auto":
|
||||||
language = self.translation.src
|
language = self.translation.src
|
||||||
embed.add_field(name="Gedetecteerde taal", value=title_case(LANGUAGES[language]))
|
embed.add_field(name="Gedetecteerde taal", value=title_case(LANGUAGES[language]))
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
"jpl table": "De huidige stand van het klassement.",
|
"jpl table": "De huidige stand van het klassement.",
|
||||||
"jpl update": "Haalt de nieuwe code voor de competitie van dit jaar op.",
|
"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.",
|
"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.",
|
"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": "Laadt [Cog] in.",
|
||||||
"load all": "Laadt alle cogs in.",
|
"load all": "Laadt alle cogs in.",
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
from functions.database import utils
|
from functions.database import utils
|
||||||
|
from functions.stringFormatters import leading_zero as lz
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
def invoked():
|
class InvocationType(IntEnum):
|
||||||
|
TextCommand = 0
|
||||||
|
SlashCommand = 1
|
||||||
|
ContextMenu = 2
|
||||||
|
|
||||||
|
|
||||||
|
def invoked(inv: InvocationType):
|
||||||
t = time.localtime()
|
t = time.localtime()
|
||||||
day_string: str = f"{t.tm_year}-{_lz(t.tm_mon)}-{_lz(t.tm_mday)}"
|
day_string: str = f"{t.tm_year}-{lz(t.tm_mon)}-{lz(t.tm_mday)}"
|
||||||
_update(day_string)
|
_update(day_string, inv)
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def _is_present(date: str) -> bool:
|
def _is_present(date: str) -> bool:
|
||||||
|
@ -43,25 +40,27 @@ def _add_date(date: str):
|
||||||
connection = utils.connect()
|
connection = utils.connect()
|
||||||
cursor = connection.cursor()
|
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()
|
connection.commit()
|
||||||
|
|
||||||
|
|
||||||
def _update(date: str):
|
def _update(date: str, inv: InvocationType):
|
||||||
"""
|
"""
|
||||||
Increase the counter for a given day
|
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):
|
if not _is_present(date):
|
||||||
_add_date(date)
|
_add_date(date)
|
||||||
return
|
|
||||||
|
|
||||||
connection = utils.connect()
|
connection = utils.connect()
|
||||||
cursor = connection.cursor()
|
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
|
UPDATE command_stats
|
||||||
SET amount = amount + 1
|
SET {column_name} = {column_name} + 1
|
||||||
WHERE day = %s
|
WHERE day = %s
|
||||||
""", (date,))
|
""", (date,))
|
||||||
connection.commit()
|
connection.commit()
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
from dislash import SlashInteraction
|
||||||
|
|
||||||
|
|
||||||
def title_case(string):
|
def title_case(string):
|
||||||
return " ".join(capitalize(word) for word in string.split(" "))
|
return " ".join(capitalize(word) for word in string.split(" "))
|
||||||
|
|
||||||
|
@ -13,3 +19,35 @@ def leading_zero(string, size=2):
|
||||||
while len(string) < size:
|
while len(string) < size:
|
||||||
string = "0" + string
|
string = "0" + string
|
||||||
return 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}"
|
||||||
|
|
10
settings.py
10
settings.py
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -31,3 +33,11 @@ TOKEN = os.getenv("TOKEN", "")
|
||||||
HOST_IPC = _to_bool(os.getenv("HOSTIPC", "false"))
|
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
|
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.")
|
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(",")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from data.snipe import Snipe
|
||||||
from discord.ext import commands, ipc
|
from discord.ext import commands, ipc
|
||||||
from dislash import InteractionClient
|
from dislash import InteractionClient
|
||||||
import os
|
import os
|
||||||
from settings import HOST_IPC
|
from settings import HOST_IPC, SLASH_TEST_GUILDS
|
||||||
from startup.init_files import check_all
|
from startup.init_files import check_all
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class Didier(commands.Bot):
|
||||||
self.remove_command("help")
|
self.remove_command("help")
|
||||||
|
|
||||||
# Create interactions client
|
# Create interactions client
|
||||||
self.interactions = InteractionClient(self, test_guilds=[728361030404538488, 880175869841277008])
|
self.interactions = InteractionClient(self, test_guilds=SLASH_TEST_GUILDS)
|
||||||
|
|
||||||
# Load all extensions
|
# Load all extensions
|
||||||
self.init_extensions()
|
self.init_extensions()
|
||||||
|
|
Loading…
Reference in New Issue