Merge pull request #90 from stijndcl/slash_commands

First slash commands & small fixes/cleanups
pull/91/head
Stijn De Clercq 2021-09-05 16:13:17 +02:00 committed by GitHub
commit 5e946ed5d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 592 additions and 245 deletions

View File

@ -19,7 +19,7 @@ ipc_client = ipc.Client(secret_key="SOME_SECRET_KEY")
@app.route("/ping", methods=["GET"]) @app.route("/ping", methods=["GET"])
async def ping(): async def ping():
""" """
Send a ping request, monitors bot latency, endpoint time, and PSQL latency Send a ping request, monitors bot latency and endpoint time
""" """
latency = await ipc_client.request("get_bot_latency") latency = await ipc_client.request("get_bot_latency")

View File

@ -163,8 +163,8 @@ class Birthdays(commands.Cog):
# Create a datetime object for this birthday # Create a datetime object for this birthday
timeString = "{}/{}/{}".format( timeString = "{}/{}/{}".format(
stringFormatters.leadingZero(str(day)), stringFormatters.leading_zero(str(day)),
stringFormatters.leadingZero(str(month)), stringFormatters.leading_zero(str(month)),
year year
) )

View File

@ -1,11 +1,8 @@
import os from data.embeds.urban_dictionary import Definition
from decorators import help from decorators import help
import discord
from discord.ext import commands from discord.ext import commands
from enums.help_categories import Category from enums.help_categories import Category
from functions import checks from functions import checks
import requests
class Define(commands.Cog): class Define(commands.Cog):
@ -19,99 +16,15 @@ class Define(commands.Cog):
@commands.command(name="Define", aliases=["UrbanDictionary", "Ud"], usage="[Woord]") @commands.command(name="Define", aliases=["UrbanDictionary", "Ud"], usage="[Woord]")
@commands.check(checks.allowedChannels) @commands.check(checks.allowedChannels)
@help.Category(category=Category.Other) @help.Category(category=Category.Other)
async def define(self, ctx, *words): async def define(self, ctx, *, query):
""" """
Command that looks up the definition of a word in the Urban Dictionary. Command that looks up the definition of a word in the Urban Dictionary.
:param ctx: Discord Context :param ctx: Discord Context
:param words: Word(s) to look up :param query: Word(s) to look up
""" """
words = list(words) embed = Definition(query).to_embed()
if len(words) == 0:
return await ctx.send("Controleer je argumenten.")
query = " ".join(words)
answer = self.lookup(query)
embed = discord.Embed(colour=discord.Colour.from_rgb(220, 255, 0))
embed.set_author(name="Urban Dictionary")
embed.add_field(name="Woord", value=answer["word"], inline=True)
embed.add_field(name="Auteur", value=answer["author"], inline=True)
embed.add_field(name="Definitie", value=self.cleanString(answer["definition"]), inline=False)
embed.add_field(name="Voorbeeld", value=self.cleanString(answer["example"]), inline=False)
embed.add_field(name="Rating", value=str(round(self.ratio(answer), 2)) + "%")
embed.add_field(name="Link naar de volledige definitie",
value="[Urban Dictionary]({})".format(str(answer["link"])))
await ctx.send(embed=embed) await ctx.send(embed=embed)
def lookup(self, word):
"""
Function that sends the API request to get the definition.
:param word: the woord to look up
:return: a dictionary representing the info of this word
"""
url = "https://mashape-community-urban-dictionary.p.rapidapi.com/define"
querystring = {"term": word}
headers = {
'x-rapidapi-host': "mashape-community-urban-dictionary.p.rapidapi.com",
'x-rapidapi-key': os.getenv("URBANDICTIONARY")
}
try:
if word.lower() == "didier":
raise Exception
response = requests.request("GET", url, headers=headers, params=querystring).json()["list"]
if len(response) > 0:
return {"word": response[0]["word"], "definition": response[0]["definition"],
"example": response[0]["example"], "thumbs_up": response[0]["thumbs_up"],
"thumbs_down": response[0]["thumbs_down"], "link": response[0]["permalink"],
"author": response[0]["author"]}
# No valid response
return self.defineDidier()
except Exception:
return self.defineDidier()
def cleanString(self, text: str):
"""
Function that cuts off definitions that are too long & strips out UD markdown
from an input string.
:param text: the input string to clean up
:return: the edited version of the string
"""
text = text.replace("[", "")
text = text.replace("]", "")
if not text:
return "N/A"
return text if len(text) < 1024 else text[:1021] + "..."
def ratio(self, dic):
"""
Function that alculates the upvote/downvote ratio of the definition.
:param dic: the dictionary representing the definition
:return: the upvote/downvote ratio (float)
"""
return (100 * int(dic["thumbs_up"])) / (int(dic["thumbs_up"]) + int(dic["thumbs_down"])) \
if int(dic["thumbs_down"]) != 0 else 100.0
def defineDidier(self):
"""
Function that returns a stock dictionary to define Didier
in case people call it, or no definition was found.
:return: a dictionary that defines Didier
"""
return {"word": "Didier", "definition": "Didier", "example": "1: Didier\n2: Hmm?", "thumbs_up": 69420,
"thumbs_down": 0, "author": "Didier",
"link": "https://upload.wikimedia.org/wikipedia/commons/a/a5"
"/Didier_Reynders_in_Iranian_Parliament_02.jpg"}
def setup(client): def setup(client):
client.add_cog(Define(client)) client.add_cog(Define(client))

View File

@ -1,15 +1,16 @@
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
from startup.didier import Didier from startup.didier import Didier
import time import time
import traceback
class Events(commands.Cog): class Events(commands.Cog):
@ -33,9 +34,6 @@ class Events(commands.Cog):
""" """
Function called when the bot is ready & done leading. Function called when the bot is ready & done leading.
""" """
# Set status
await self.client.change_presence(status=discord.Status.online, activity=discord.Game(STATUS_MESSAGE))
print(READY_MESSAGE) print(READY_MESSAGE)
# Add constants to the client as a botvar # Add constants to the client as a botvar
@ -87,12 +85,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):
@ -101,8 +96,8 @@ class Events(commands.Cog):
:param ctx: Discord Context :param ctx: Discord Context
:param err: the error thrown :param err: the error thrown
""" """
# Zandbak Didier shouldn't spam the error logs # Debugging Didier shouldn't spam the error logs
if self.client.user.id == int(constants.coolerDidierId): if self.client.user.id != int(constants.didierId):
raise err raise err
# Don't handle commands that have their own custom error handler # Don't handle commands that have their own custom error handler
@ -123,14 +118,26 @@ 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, 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() @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="Error")
embed.add_field(name="Command:", value="{} in {}: {}".format(ctx.author.display_name, embed.add_field(name=f"{error_type}:", 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)

View File

@ -37,7 +37,7 @@ class Faq(commands.Cog):
return await self.faqCategory(ctx, (constants.faq_channels[ctx.channel.id],)) return await self.faqCategory(ctx, (constants.faq_channels[ctx.channel.id],))
# List of all categories with the first letter capitalized # List of all categories with the first letter capitalized
resp = [stringFormatters.titleCase(cat[0]) for cat in faq.getCategories()] resp = [stringFormatters.title_case(cat[0]) for cat in faq.getCategories()]
# Sort alphabetically # Sort alphabetically
resp.sort() resp.sort()
@ -146,7 +146,7 @@ class Faq(commands.Cog):
resp.sort(key=lambda x: int(x[0])) resp.sort(key=lambda x: int(x[0]))
embed = discord.Embed(colour=discord.Colour.blue()) embed = discord.Embed(colour=discord.Colour.blue())
embed.set_author(name="FAQ {}".format(stringFormatters.titleCase(category))) embed.set_author(name="FAQ {}".format(stringFormatters.title_case(category)))
# Add everything into the embed # Add everything into the embed
for i, pair in enumerate(resp): for i, pair in enumerate(resp):

View File

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

View File

@ -120,7 +120,7 @@ class Fun(commands.Cog):
memeList = memes.getAllMemes() memeList = memes.getAllMemes()
# Turn the list into a list of [Name: fields] # Turn the list into a list of [Name: fields]
memeList = [": ".join([stringFormatters.titleCase(meme[1]), memeList = [": ".join([stringFormatters.title_case(meme[1]),
str(meme[2])]) for meme in sorted(memeList, key=lambda x: x[1])] str(meme[2])]) for meme in sorted(memeList, key=lambda x: x[1])]
pages = paginatedLeaderboard.Pages(source=paginatedLeaderboard.Source(memeList, "Memes", discord.Colour.blue()), pages = paginatedLeaderboard.Pages(source=paginatedLeaderboard.Source(memeList, "Memes", discord.Colour.blue()),

View File

@ -1,8 +1,7 @@
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
from functions.scrapers.google import google_search from functions.scrapers.google import google_search, create_google_embed
class Google(commands.Cog): class Google(commands.Cog):
@ -19,33 +18,12 @@ class Google(commands.Cog):
if not query: if not query:
return await ctx.reply("Je hebt geen query opgegeven.", mention_author=True) return await ctx.reply("Je hebt geen query opgegeven.", mention_author=True)
results, status = google_search(" ".join(query)) result = google_search(" ".join(query))
if results is None: if not result.results:
return await ctx.send("Er ging iets fout (Response {})".format(status)) return await ctx.send("Er ging iets fout (Response {})".format(result.status_code))
# Filter out all Nones
elements = list(filter(lambda x: x is not None, results))
embed = discord.Embed(colour=discord.Colour.blue())
embed.set_author(name="Google Search")
# Empty list of results
if len(elements) == 0:
embed.description = "Geen resultaten gevonden."
return await ctx.reply(embed=embed, mention_author=False)
# Cut excess results out
if len(elements) > 10:
elements = elements[:10]
links = []
for index, (link, title) in enumerate(elements):
links.append("{}: [{}]({})".format(index + 1, title, link))
embed.description = "\n".join(links)
embed = create_google_embed(result)
await ctx.reply(embed=embed, mention_author=False) await ctx.reply(embed=embed, mention_author=False)

View File

@ -182,15 +182,13 @@ class ModCommands(commands.Cog):
embed = discord.Embed(colour=discord.Colour.blue()) embed = discord.Embed(colour=discord.Colour.blue())
embed.set_author(name=user.display_name, icon_url=user.avatar_url) embed.set_author(name=user.display_name, icon_url=user.avatar_url)
embed.add_field(name="Discriminator", value="#{}".format(user.discriminator)) embed.add_field(name="Discriminator", value=f"#{user.discriminator}")
embed.add_field(name="Discord id", value=user.id) embed.add_field(name="Discord id", value=user.id)
embed.add_field(name="Bot", value="Nee" if not user.bot else "Ja") embed.add_field(name="Bot", value="Nee" if not user.bot else "Ja")
created_local = timeFormatters.epochToDate(user.created_at.timestamp()) created_local = timeFormatters.epochToDate(user.created_at.timestamp())
embed.add_field(name="Account aangemaakt", value="{}\n({} geleden)".format( embed.add_field(name="Account aangemaakt", value=f"<t:{round(created_local['dateDT'].timestamp())}:R>", inline=False)
created_local["date"], timeFormatters.diffYearBasisString(round(created_local["dateDT"].timestamp()))
), inline=False)
# Check if the user is in the current guild # Check if the user is in the current guild
if ctx.guild is not None: if ctx.guild is not None:
@ -199,9 +197,8 @@ class ModCommands(commands.Cog):
if member_instance is not None: if member_instance is not None:
joined_local = timeFormatters.epochToDate(member_instance.joined_at.timestamp()) joined_local = timeFormatters.epochToDate(member_instance.joined_at.timestamp())
embed.add_field(name="Lid geworden van {} op".format(ctx.guild.name), value="{}\n({} Geleden)".format( embed.add_field(name=f"Lid geworden van {ctx.guild.name}",
joined_local["date"], timeFormatters.diffYearBasisString(round(joined_local["dateDT"].timestamp())) value=f"<t:{round(joined_local['dateDT'].timestamp())}:R>")
))
embed.add_field(name="Mention String", value=member_instance.mention, inline=False) embed.add_field(name="Mention String", value=member_instance.mention, inline=False)

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.") 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):
@ -62,7 +62,7 @@ class School(commands.Cog):
return await ctx.send(embed=s.create_schedule().to_embed()) return await ctx.send(embed=s.create_schedule().to_embed())
@commands.command(name="Pin", usage="[Message]") @commands.command(name="Pin", usage="[Message]")
@help.Category(category=Category.School) @help.Category(category=Category.Other)
async def pin(self, ctx, message: discord.Message): async def pin(self, ctx, message: discord.Message):
# In case people abuse, check if they're blacklisted # In case people abuse, check if they're blacklisted
blacklist = [] blacklist = []

View File

@ -0,0 +1,24 @@
from discord.ext import commands
from dislash import SlashInteraction, slash_command, Option, OptionType
from data.embeds.urban_dictionary import Definition
from startup.didier import Didier
class DefineSlash(commands.Cog):
def __init__(self, client: Didier):
self.client: Didier = client
@slash_command(name="define",
description="Urban Dictionary",
options=[
Option("query", "Search query", OptionType.STRING, required=True)
]
)
async def _define_slash(self, interaction: SlashInteraction, query):
embed = Definition(query).to_embed()
await interaction.reply(embed=embed)
def setup(client: Didier):
client.add_cog(DefineSlash(client))

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

@ -0,0 +1,28 @@
from discord.ext import commands
from dislash import slash_command, SlashInteraction, Option, OptionType
from functions.scrapers.google import google_search, create_google_embed
from startup.didier import Didier
class GoogleSlash(commands.Cog):
def __init__(self, client: Didier):
self.client: Didier = client
@slash_command(name="google",
description="Google search",
options=[
Option("query", "Search query", OptionType.STRING, required=True)
]
)
async def _google_slash(self, interaction: SlashInteraction, query: str):
result = google_search(query)
if not result.results:
return await interaction.reply("Er ging iets fout (Response {})".format(result.status_code))
embed = create_google_embed(result)
await interaction.reply(embed=embed)
def setup(client: Didier):
client.add_cog(GoogleSlash(client))

View File

@ -0,0 +1,27 @@
from discord.ext import commands
from dislash import SlashInteraction, slash_command, Option, OptionType
from data.embeds.translate import Translation
from startup.didier import Didier
class TranslateSlash(commands.Cog):
def __init__(self, client: Didier):
self.client: Didier = client
@slash_command(
name="translate",
description="Google Translate",
options=[
Option("text", "Tekst om te vertalen", OptionType.STRING, required=True),
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, 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())
def setup(client: Didier):
client.add_cog(TranslateSlash(client))

View File

@ -35,7 +35,7 @@ class Tasks(commands.Cog):
# Don't do it multiple times a day if bot dc's, ... # Don't do it multiple times a day if bot dc's, ...
with open("files/lastTasks.json", "r") as fp: with open("files/lastTasks.json", "r") as fp:
lastTasks = json.load(fp) lastTasks = json.load(fp)
if int(self.getCurrentHour()) == 0 and int(time.time()) - int(lastTasks["interest"]) > 10000: if int(self.getCurrentHour()) == 4 and int(time.time()) - int(lastTasks["interest"]) > 10000:
users = currency.getAllRows() users = currency.getAllRows()
bitcoinPrice = self.getCurrentBitcoinPrice() bitcoinPrice = self.getCurrentBitcoinPrice()
for user in users: for user in users:
@ -188,7 +188,7 @@ class Tasks(commands.Cog):
# Don't do it multiple times a day if bot dc's, ... # Don't do it multiple times a day if bot dc's, ...
with open("files/lastTasks.json", "r") as fp: with open("files/lastTasks.json", "r") as fp:
lastTasks = json.load(fp) lastTasks = json.load(fp)
if int(self.getCurrentHour()) == 7 and int(time.time()) - int(lastTasks["remind"]) > 10000: if int(self.getCurrentHour()) == 4 and int(time.time()) - int(lastTasks["remind"]) > 10000:
reminders = Reminders() reminders = Reminders()
weekday = self.getCurrentWeekday() weekday = self.getCurrentWeekday()

View File

@ -2,7 +2,7 @@ from decorators import help
import discord import discord
from discord.ext import commands from discord.ext import commands
from enums.help_categories import Category from enums.help_categories import Category
from functions.stringFormatters import titleCase as tc from functions.stringFormatters import title_case as tc
from googletrans import Translator, LANGUAGES from googletrans import Translator, LANGUAGES
import re import re

View File

@ -0,0 +1,61 @@
import discord
from googletrans import Translator, LANGUAGES
from functions.stringFormatters import title_case
from typing import Optional
class Translation:
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, fr, to)
def translate(self, query: str, fr: str, to: str):
"""
Translate [query] into [to]
"""
try:
translator = Translator()
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
def _create_error_embed(self, message):
embed = discord.Embed(colour=discord.Colour.red())
embed.set_author(name="Didier Translate")
embed.description = message
self.embed = embed
def to_embed(self) -> discord.Embed:
# There's an error embed to show
if self.embed is not None:
return self.embed
embed = discord.Embed(colour=discord.Colour.blue())
embed.set_author(name="Didier Translate")
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))
embed.add_field(name="Origineel ({})".format(self.translation.src.upper()), value=self.text, inline=False)
embed.add_field(name="Vertaling ({})".format(self.to.upper()), value=self.translation.text)
return embed

View File

@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
from discord import Embed, Colour from discord import Embed, Colour
from functions.stringFormatters import leadingZero as lz from functions.stringFormatters import leading_zero as lz
from functions.timeFormatters import intToWeekday from functions.timeFormatters import intToWeekday
from markdownify import markdownify as md from markdownify import markdownify as md
import pytz import pytz

View File

@ -0,0 +1,112 @@
import discord
import os
import requests
from typing import Dict
class Definition:
def __init__(self, query: str):
self.query = query
self.definition = Definition.lookup(query)
@staticmethod
def lookup(word) -> Dict:
"""
Function that sends the API request to get the definition.
:param word: the woord to look up
:return: a dictionary representing the info of this word
"""
url = "https://mashape-community-urban-dictionary.p.rapidapi.com/define"
querystring = {"term": word}
headers = {
'x-rapidapi-host': "mashape-community-urban-dictionary.p.rapidapi.com",
'x-rapidapi-key': os.getenv("URBANDICTIONARY")
}
try:
if word.lower() == "didier":
return Definition.define_didier()
response = requests.get(url, headers=headers, params=querystring).json()["list"]
if len(response) > 0:
return {"word": response[0]["word"], "definition": response[0]["definition"],
"example": response[0]["example"], "thumbs_up": response[0]["thumbs_up"],
"thumbs_down": response[0]["thumbs_down"], "link": response[0]["permalink"],
"author": response[0]["author"]}
# No valid response
return {}
except Exception:
return Definition.define_didier()
@staticmethod
def clean_string(text: str):
"""
Function that cuts off definitions that are too long & strips out UD markdown
from an input string.
:param text: the input string to clean up
:return: the edited version of the string
"""
text = text.replace("[", "")
text = text.replace("]", "")
if not text:
return "N/A"
return text if len(text) < 1024 else text[:1021] + "..."
@staticmethod
def ratio(dic) -> float:
"""
Function that calculates the upvote/downvote ratio of the definition.
:param dic: the dictionary representing the definition
:return: the upvote/downvote ratio (float)
"""
return (100 * int(dic["thumbs_up"])) / (int(dic["thumbs_up"]) + int(dic["thumbs_down"])) \
if int(dic["thumbs_down"]) != 0 else 100.0
@staticmethod
def define_didier() -> Dict:
"""
Function that returns a stock dictionary to define Didier
in case people call it, or no definition was found.
:return: a dictionary that defines Didier
"""
return {"word": "Didier", "definition": "Didier", "example": "1: Didier\n2: Hmm?", "thumbs_up": 69420,
"thumbs_down": 0, "author": "Didier",
"link": "https://upload.wikimedia.org/wikipedia/commons/a/a5"
"/Didier_Reynders_in_Iranian_Parliament_02.jpg"}
def to_embed(self) -> discord.Embed:
"""
Create an embed for this definition
"""
# No results found
if not self.definition:
return self._nothing_found_embed()
embed = discord.Embed(colour=discord.Colour.from_rgb(220, 255, 0))
embed.set_author(name="Urban Dictionary")
embed.add_field(name="Woord", value=self.definition["word"], inline=True)
embed.add_field(name="Auteur", value=self.definition["author"], inline=True)
embed.add_field(name="Definitie", value=Definition.clean_string(self.definition["definition"]), inline=False)
embed.add_field(name="Voorbeeld", value=Definition.clean_string(self.definition["example"]), inline=False)
embed.add_field(name="Rating", value=str(round(Definition.ratio(self.definition), 2)) + "%")
embed.add_field(name="Link naar de volledige definitie",
value="[Urban Dictionary]({})".format(str(self.definition["link"])))
return embed
def _nothing_found_embed(self) -> discord.Embed:
"""
Special embed when no results could be found
"""
embed = discord.Embed(colour=discord.Colour.red(), title=self.query[:256])
embed.set_author(name="Urban Dictionary")
embed.description = "Geen resultaten gevonden"
return embed

View File

@ -1,18 +1,23 @@
import discord import discord
from dotenv import load_dotenv from dotenv import load_dotenv
from functions.prefixes import get_prefix from functions.prefixes import get_prefix
from settings import TOKEN from settings import STATUS_MESSAGE, TOKEN
from startup.didier import Didier from startup.didier import Didier
if __name__ == "__main__": if __name__ == "__main__":
load_dotenv(verbose=True) load_dotenv(verbose=True)
# Activities
activity = discord.Activity(type=discord.ActivityType.playing, name=STATUS_MESSAGE)
status = discord.Status.online
# Configure intents (1.5.0) # Configure intents (1.5.0)
intents = discord.Intents.default() intents = discord.Intents.default()
intents.members = True intents.members = True
client = Didier(command_prefix=get_prefix, case_insensitive=True, intents=intents) client = Didier(command_prefix=get_prefix, case_insensitive=True, intents=intents, activity=activity, status=status)
# Run IPC server if necessary
if client.ipc is not None: if client.ipc is not None:
client.ipc.start() client.ipc.start()

View File

@ -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.",

View File

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

View File

@ -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()

View File

@ -1,11 +1,8 @@
#!/usr/bin/env python3
import datetime import datetime
import requests import requests
import sys
def etenScript(weekDag): def etenScript(weekDag, resto: str = "sterre"):
# What day # What day
weekdagen = ('ma', 'di', 'wo', 'do', 'vr', 'za', 'zo') weekdagen = ('ma', 'di', 'wo', 'do', 'vr', 'za', 'zo')
deltas = {'morgen': 1, deltas = {'morgen': 1,
@ -26,7 +23,7 @@ def etenScript(weekDag):
# Fetch from API # Fetch from API
try: try:
menu = requests.get(f"https://zeus.ugent.be/hydra/api/2.0/resto/menu/nl-sterre/{d.year}/{d.month}/{d.day}.json").json() menu = requests.get(f"https://zeus.ugent.be/hydra/api/2.0/resto/menu/nl-{resto}/{d.year}/{d.month}/{d.day}.json").json()
if not menu["meals"]: if not menu["meals"]:
raise Exception() raise Exception()

View File

@ -5,7 +5,7 @@ from datetime import datetime
from enum import Enum from enum import Enum
from functions.timeFormatters import fromString from functions.timeFormatters import fromString
from functions.scrapers.sporza import getJPLMatches, getJPLTable from functions.scrapers.sporza import getJPLMatches, getJPLTable
from functions.stringFormatters import leadingZero from functions.stringFormatters import leading_zero
import re import re
from requests import get from requests import get
import tabulate import tabulate
@ -39,11 +39,11 @@ class Match:
Parse class attributes out of a dictionary returned from an API request 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 # 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.home = self.matchDict[Navigation.HomeTeam.value][Navigation.Name.value]
self.away = self.matchDict[Navigation.AwayTeam.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.homeScore = self.matchDict[Navigation.HomeScore.value]
self.awayScore = self.matchDict[Navigation.AwayScore.value] self.awayScore = self.matchDict[Navigation.AwayScore.value]
@ -53,9 +53,9 @@ class Match:
self.start = None self.start = None
self.date = self.start.strftime("%d/%m") if self.start is not None else "Uitgesteld" 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 Gets the string representation for the status of this match
""" """
@ -80,7 +80,7 @@ class Match:
return statusses[status.lower()] return statusses[status.lower()]
def _getWeekday(self): def _get_weekday(self):
""" """
Gets the day of the week this match is played on 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"] days = ["Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"]
return days[day] 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 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 Returns a string representing the scoreboard
""" """
@ -102,12 +102,12 @@ class Match:
return "??" return "??"
# No score to show yet, show time when the match starts # No score to show yet, show time when the match starts
if not self._hasStarted(): if not self._has_started():
return "{}:{}".format(leadingZero(str(self.start.hour)), leadingZero(str(self.start.minute))) return "{}:{}".format(leading_zero(str(self.start.hour)), leading_zero(str(self.start.minute)))
return "{} - {}".format(self.homeScore, self.awayScore) 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] return self.status not in [Status.AfterToday.value, Status.NotStarted.value, Status.Postponed.value]
@ -128,7 +128,7 @@ class Navigation(Enum):
Name = "name" Name = "name"
def getMatches(matchweek: int): def get_matches(matchweek: int):
""" """
Function that constructs the list of matches for a given matchweek 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." return "Er ging iets fout. Probeer het later opnieuw."
matches = list(map(Match, current_day)) 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) header = "Jupiler Pro League - Speeldag {}".format(matchweek)
table = tabulate.tabulate(matches, headers=["Dag", "Datum", "Thuis", "Stand", "Uit", "Tijd"]) 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) return "```{}\n\n{}```".format(header, table)
def getTable(): def get_table():
""" """
Function that constructs the current table of the JPL Function that constructs the current table of the JPL
""" """
@ -157,7 +157,7 @@ def getTable():
return "Er ging iets fout. Probeer het later opnieuw." return "Er ging iets fout. Probeer het later opnieuw."
# Format every row to work for Tabulate # 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" header = "Jupiler Pro League Klassement"
table = tabulate.tabulate(formatted, headers=["#", "Ploeg", "Punten", "M", "M+", "M-", "M=", "D+", "D-", "D+/-"]) 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) 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 Function that formats a row into a list for Tabulate to use
""" """

View File

@ -1,9 +1,23 @@
from typing import List
import discord
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from dataclasses import dataclass
from requests import get from requests import get
from urllib.parse import urlencode from urllib.parse import urlencode, unquote_plus
def google_search(query): @dataclass
class SearchResult:
status_code: int
query: str
results: List[str]
def __post_init__(self):
self.query = unquote_plus(self.query[2:])
def google_search(query) -> SearchResult:
""" """
Function to get Google search results Function to get Google search results
""" """
@ -17,7 +31,7 @@ def google_search(query):
resp = get("https://www.google.com/search?{}&num=20&hl=en".format(query), headers=headers) resp = get("https://www.google.com/search?{}&num=20&hl=en".format(query), headers=headers)
if resp.status_code != 200: if resp.status_code != 200:
return None, resp.status_code return SearchResult(resp.status_code, query, [])
bs = BeautifulSoup(resp.text, "html.parser") bs = BeautifulSoup(resp.text, "html.parser")
@ -28,11 +42,48 @@ def google_search(query):
link = element.find("a", href=True) link = element.find("a", href=True)
title = element.find("h3") title = element.find("h3")
if link is None or title is None: if link is None or not link["href"].startswith(("http://", "https://",)) or title is None:
return None return None
return link["href"], title.text return link["href"], title.text
divs = bs.find_all("div", attrs={"class": "g"}) divs = bs.find_all("div", attrs={"class": "g"})
return list(getContent(d) for d in divs), 200 results = list(getContent(d) for d in divs)
# Filter out Nones
results = list(filter(lambda x: x is not None, results))
# Map to urls
links = []
for (l, t) in results:
links.append(f"[{t}]({l})")
return SearchResult(200, query, links[:10])
def create_google_embed(result: SearchResult) -> discord.Embed:
embed = discord.Embed(colour=discord.Colour.blue())
embed.set_author(name="Google Search")
# Empty list of results
if len(result.results) == 0:
embed.colour = discord.Colour.red()
embed.description = "Geen resultaten gevonden."
return embed
# Add results into a field
links = []
for index, link in enumerate(result.results):
links.append(f"{index + 1}: {link}")
embed.description = "\n".join(links)
# Add query into embed
if len(result.query) > 256:
embed.title = result.query[:253] + "..."
else:
embed.title = result.query
return embed

View File

@ -1,4 +1,10 @@
def titleCase(string): 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(" ")) return " ".join(capitalize(word) for word in string.split(" "))
@ -8,8 +14,40 @@ def capitalize(string):
return string[0].upper() return string[0].upper()
def leadingZero(string, size=2): def leading_zero(string, size=2):
string = str(string) string = str(string)
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}"

View File

@ -161,14 +161,14 @@ def fromString(timeString: str, formatString="%d/%m/%Y", tzinfo=pytz.timezone("E
def fromArray(data: List[int]) -> datetime: def fromArray(data: List[int]) -> datetime:
day = stringFormatters.leadingZero(str(data[0])) day = stringFormatters.leading_zero(str(data[0]))
month = stringFormatters.leadingZero(str(data[1])) month = stringFormatters.leading_zero(str(data[1]))
year = str(data[2]) year = str(data[2])
if len(data) == 6: if len(data) == 6:
hour = stringFormatters.leadingZero(str(data[3])) hour = stringFormatters.leading_zero(str(data[3]))
minute = stringFormatters.leadingZero(str(data[4])) minute = stringFormatters.leading_zero(str(data[4]))
second = stringFormatters.leadingZero(str(data[5])) second = stringFormatters.leading_zero(str(data[5]))
return fromString(f"{day}/{month}/{year} {hour}:{minute}:{second}", formatString="%d/%m/%Y %H:%M:%S") return fromString(f"{day}/{month}/{year} {hour}:{minute}:{second}", formatString="%d/%m/%Y %H:%M:%S")

View File

@ -18,4 +18,7 @@ Quart-CORS==0.5.0
attrs~=21.2.0 attrs~=21.2.0
dacite~=1.6.0 dacite~=1.6.0
pytest==6.2.4 pytest==6.2.4
markdownify==0.9.2 markdownify==0.9.2
# Experimental package for slash commands & menus
dislash.py==1.4.9

View File

@ -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(",")
)
)

View File

@ -1,7 +1,8 @@
from data.snipe import Snipe from data.snipe import Snipe
from discord.ext import commands, ipc from discord.ext import commands, ipc
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
@ -10,6 +11,8 @@ class Didier(commands.Bot):
""" """
Main Bot class for Didier Main Bot class for Didier
""" """
# Reference to interactions client
interactions: InteractionClient
# Dict to store the most recent Snipe info per channel # Dict to store the most recent Snipe info per channel
snipe: Dict[int, Snipe] = {} snipe: Dict[int, Snipe] = {}
@ -29,6 +32,9 @@ class Didier(commands.Bot):
# Remove default help command # Remove default help command
self.remove_command("help") self.remove_command("help")
# Create interactions client
self.interactions = InteractionClient(self, test_guilds=SLASH_TEST_GUILDS)
# Load all extensions # Load all extensions
self.init_extensions() self.init_extensions()
@ -41,9 +47,24 @@ class Didier(commands.Bot):
self.load_extension(f"cogs.{ext}") self.load_extension(f"cogs.{ext}")
# Load all remaining cogs # Load all remaining cogs
for file in os.listdir("./cogs"): self._init_directory("./cogs")
if file.endswith(".py") and not (file.startswith(self._preload)):
self.load_extension("cogs.{}".format(file[:-3])) def _init_directory(self, path: str):
"""
Load all cogs from a directory
"""
# Path to pass into load_extension
load_path = path[2:].replace("/", ".")
for file in os.listdir(path):
# Python file
if file.endswith(".py"):
if not file.startswith(self._preload):
self.load_extension(f"{load_path}.{file[:-3]}")
elif os.path.isdir(new_path := f"{path}/{file}"):
# Subdirectory
# Also walrus operator hype
self._init_directory(new_path)
async def on_ipc_ready(self): async def on_ipc_ready(self):
print("IPC server is ready.") print("IPC server is ready.")

View File

View File

@ -0,0 +1,45 @@
from data.embeds.urban_dictionary import Definition
import unittest
class TestUD(unittest.TestCase):
def test_clean_string(self):
self.assertEqual(
Definition.clean_string("A definition [with links] to other [definitions]"),
"A definition with links to other definitions"
)
no_processing = "A string that needs no processing."
self.assertEqual(Definition.clean_string(no_processing), no_processing)
long_string = "A very long string that hopefully exceeds the 1024 character limit for embed field values, " \
"in order to test if the truncation part of this specific function works as expected. " \
"The issue is that coming up with a string that exceeds the 1024 embed field value character " \
"limit is quite tedious, so I have no idea how I plan on ever finishing this." \
"As of the writing of this sentence, I'm only a third of the way there." \
"Crazy. I could probably just toss some lorem ipsum in there, but that would be no fun." \
"Or would it? Hey GitHub, Didier here." \
"Instead I would like to take this opportunity to out my frustrations on the abomination of a " \
"\"language\" that is Haskell. You see, Haskell is just terrible and useless. " \
"It truly does pose the bane of my existence, and I deeply hope that I will never have to use it " \
"ever again in my life. Good thing I somehow managed to pass that class, otherwise I would've " \
"probably collapsed mentally on the spot. As it turns out, though, this sentence is already in the " \
"900 character range, so I don't have much of a reason to continue writing about the worst " \
"invention humanity has ever come up with."
self.assertGreater(len(long_string), 1024)
self.assertEqual(len(Definition.clean_string(long_string)), 1024)
self.assertEqual(Definition.clean_string(long_string)[-3:], "...")
def test_ratio(self):
dic = {
"thumbs_up": 5,
"thumbs_down": 0
}
self.assertEqual(Definition.ratio(dic), 100.0)
dic["thumbs_down"] = 5
self.assertEqual(Definition.ratio(dic), 50.0)
dic["thumbs_up"] = 0
self.assertEqual(Definition.ratio(dic), 0)