diff --git a/cogs/birthdays.py b/cogs/birthdays.py index 2c3c284..8c0f351 100644 --- a/cogs/birthdays.py +++ b/cogs/birthdays.py @@ -163,8 +163,8 @@ class Birthdays(commands.Cog): # Create a datetime object for this birthday timeString = "{}/{}/{}".format( - stringFormatters.leadingZero(str(day)), - stringFormatters.leadingZero(str(month)), + stringFormatters.leading_zero(str(day)), + stringFormatters.leading_zero(str(month)), year ) diff --git a/cogs/define.py b/cogs/define.py index 23eba64..26adb40 100644 --- a/cogs/define.py +++ b/cogs/define.py @@ -1,11 +1,8 @@ -import os - +from data.embeds.urban_dictionary import Definition from decorators import help -import discord from discord.ext import commands from enums.help_categories import Category from functions import checks -import requests class Define(commands.Cog): @@ -19,99 +16,15 @@ class Define(commands.Cog): @commands.command(name="Define", aliases=["UrbanDictionary", "Ud"], usage="[Woord]") @commands.check(checks.allowedChannels) @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. :param ctx: Discord Context - :param words: Word(s) to look up + :param query: Word(s) to look up """ - words = list(words) - 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"]))) - + embed = Definition(query).to_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): client.add_cog(Define(client)) diff --git a/cogs/faq.py b/cogs/faq.py index 8817b29..1b198fe 100644 --- a/cogs/faq.py +++ b/cogs/faq.py @@ -37,7 +37,7 @@ class Faq(commands.Cog): return await self.faqCategory(ctx, (constants.faq_channels[ctx.channel.id],)) # 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 resp.sort() @@ -146,7 +146,7 @@ class Faq(commands.Cog): resp.sort(key=lambda x: int(x[0])) 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 for i, pair in enumerate(resp): diff --git a/cogs/fun.py b/cogs/fun.py index 8d7a381..11b0a21 100644 --- a/cogs/fun.py +++ b/cogs/fun.py @@ -120,7 +120,7 @@ class Fun(commands.Cog): memeList = memes.getAllMemes() # 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])] pages = paginatedLeaderboard.Pages(source=paginatedLeaderboard.Source(memeList, "Memes", discord.Colour.blue()), diff --git a/cogs/google.py b/cogs/google.py index 310403d..429b4f4 100644 --- a/cogs/google.py +++ b/cogs/google.py @@ -1,30 +1,8 @@ import discord from discord.ext import commands -from dislash import slash_command, SlashInteraction, Option, OptionType from decorators import help from enums.help_categories import Category -from functions.scrapers.google import google_search, SearchResult - - -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) - - return embed +from functions.scrapers.google import google_search, create_google_embed class Google(commands.Cog): @@ -35,22 +13,6 @@ class Google(commands.Cog): def cog_check(self, ctx): return not self.client.locked - @slash_command(name="google", - description="Google search", - options=[ - Option("query", "Search query", OptionType.STRING, required=True) - ], - guild_ids=[880175869841277008] - ) - 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) - @commands.command(name="Google", aliases=["Gtfm", "Search"], usage="[Query]", case_insensitive=True) @help.Category(Category.Other) async def google(self, ctx, *query): @@ -62,7 +24,7 @@ class Google(commands.Cog): if not result.results: return await ctx.send("Er ging iets fout (Response {})".format(result.status_code)) - embed = _create_google_embed(result) + embed = create_google_embed(result) await ctx.reply(embed=embed, mention_author=False) diff --git a/cogs/slash/define_slash.py b/cogs/slash/define_slash.py new file mode 100644 index 0000000..55899d8 --- /dev/null +++ b/cogs/slash/define_slash.py @@ -0,0 +1,25 @@ +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) + ], + guild_ids=[728361030404538488, 880175869841277008] + ) + 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)) diff --git a/cogs/slash/google_slash.py b/cogs/slash/google_slash.py new file mode 100644 index 0000000..fe9ba61 --- /dev/null +++ b/cogs/slash/google_slash.py @@ -0,0 +1,29 @@ +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) + ], + guild_ids=[728361030404538488, 880175869841277008] + ) + 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)) diff --git a/cogs/slash/translate_slash.py b/cogs/slash/translate_slash.py new file mode 100644 index 0000000..226aa0d --- /dev/null +++ b/cogs/slash/translate_slash.py @@ -0,0 +1,26 @@ +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("to", "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()) + await interaction.reply(embed=translation.to_embed()) + + +def setup(client: Didier): + client.add_cog(TranslateSlash(client)) diff --git a/cogs/translate.py b/cogs/translate.py index e607ca8..74186b7 100644 --- a/cogs/translate.py +++ b/cogs/translate.py @@ -2,7 +2,7 @@ from decorators import help import discord from discord.ext import commands 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 import re diff --git a/data/embeds/translate.py b/data/embeds/translate.py new file mode 100644 index 0000000..86ade0e --- /dev/null +++ b/data/embeds/translate.py @@ -0,0 +1,54 @@ +import discord +from googletrans import Translator, LANGUAGES +from functions.stringFormatters import title_case +from typing import Optional + + +class Translation: + def __init__(self, text: str, to: str): + self.text = text + self.to = to + self.embed: Optional[discord.Embed] = None + self.translation = None + + self.translate(text, to) + + def translate(self, query: str, to: str): + """ + Translate [query] into [to] + """ + try: + translator = Translator() + self.translation = translator.translate(query, to, "auto") + except ValueError as e: + message = str(e) + + if "destination" in message: + self._create_error_embed(f"{title_case(to)} is geen geldige taal.") + + 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") + + 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 diff --git a/data/embeds/ufora.py b/data/embeds/ufora.py index c445e1b..613c932 100644 --- a/data/embeds/ufora.py +++ b/data/embeds/ufora.py @@ -1,6 +1,6 @@ from datetime import datetime 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 markdownify import markdownify as md import pytz diff --git a/data/embeds/urban_dictionary.py b/data/embeds/urban_dictionary.py new file mode 100644 index 0000000..c65017e --- /dev/null +++ b/data/embeds/urban_dictionary.py @@ -0,0 +1,113 @@ +import os +from typing import Dict + +import discord +import requests + + +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": + raise Exception + + 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 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 + + @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 diff --git a/functions/football.py b/functions/football.py index 0648096..59edf45 100644 --- a/functions/football.py +++ b/functions/football.py @@ -5,7 +5,7 @@ from datetime import datetime from enum import Enum from functions.timeFormatters import fromString from functions.scrapers.sporza import getJPLMatches, getJPLTable -from functions.stringFormatters import leadingZero +from functions.stringFormatters import leading_zero import re from requests import get import tabulate @@ -103,7 +103,7 @@ class Match: # No score to show yet, show time when the match starts if not self._hasStarted(): - 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) diff --git a/functions/scrapers/google.py b/functions/scrapers/google.py index 71fec5d..a6e6546 100644 --- a/functions/scrapers/google.py +++ b/functions/scrapers/google.py @@ -1,16 +1,21 @@ -from typing import Optional, List +from typing import List +import discord from bs4 import BeautifulSoup from dataclasses import dataclass from requests import get -from urllib.parse import urlencode +from urllib.parse import urlencode, unquote_plus @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: """ @@ -26,7 +31,7 @@ def google_search(query) -> SearchResult: resp = get("https://www.google.com/search?{}&num=20&hl=en".format(query), headers=headers) if resp.status_code != 200: - return SearchResult(resp.status_code, []) + return SearchResult(resp.status_code, query, []) bs = BeautifulSoup(resp.text, "html.parser") @@ -51,7 +56,34 @@ def google_search(query) -> SearchResult: # Map to urls links = [] - for (link, title) in results: - links.append(f"[{title}]({link})") + for (l, t) in results: + links.append(f"[{t}]({l})") - return SearchResult(200, links[:10]) + 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 diff --git a/functions/stringFormatters.py b/functions/stringFormatters.py index c38c3c3..b1646cf 100644 --- a/functions/stringFormatters.py +++ b/functions/stringFormatters.py @@ -1,4 +1,4 @@ -def titleCase(string): +def title_case(string): return " ".join(capitalize(word) for word in string.split(" ")) @@ -8,7 +8,7 @@ def capitalize(string): return string[0].upper() -def leadingZero(string, size=2): +def leading_zero(string, size=2): string = str(string) while len(string) < size: string = "0" + string diff --git a/functions/timeFormatters.py b/functions/timeFormatters.py index 4460156..1d7788a 100644 --- a/functions/timeFormatters.py +++ b/functions/timeFormatters.py @@ -161,14 +161,14 @@ def fromString(timeString: str, formatString="%d/%m/%Y", tzinfo=pytz.timezone("E def fromArray(data: List[int]) -> datetime: - day = stringFormatters.leadingZero(str(data[0])) - month = stringFormatters.leadingZero(str(data[1])) + day = stringFormatters.leading_zero(str(data[0])) + month = stringFormatters.leading_zero(str(data[1])) year = str(data[2]) if len(data) == 6: - hour = stringFormatters.leadingZero(str(data[3])) - minute = stringFormatters.leadingZero(str(data[4])) - second = stringFormatters.leadingZero(str(data[5])) + hour = stringFormatters.leading_zero(str(data[3])) + minute = stringFormatters.leading_zero(str(data[4])) + second = stringFormatters.leading_zero(str(data[5])) return fromString(f"{day}/{month}/{year} {hour}:{minute}:{second}", formatString="%d/%m/%Y %H:%M:%S") diff --git a/startup/didier.py b/startup/didier.py index 9f5a314..850080c 100644 --- a/startup/didier.py +++ b/startup/didier.py @@ -47,9 +47,24 @@ class Didier(commands.Bot): self.load_extension(f"cogs.{ext}") # Load all remaining cogs - for file in os.listdir("./cogs"): - if file.endswith(".py") and not (file.startswith(self._preload)): - self.load_extension("cogs.{}".format(file[:-3])) + self._init_directory("./cogs") + + 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): print("IPC server is ready.")