From 690040ba9cdde3c973a7a89db6eb752bc29ebeee Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 5 Sep 2021 18:41:23 -0400 Subject: Move to utilities folder as recommended by Xith --- bot/exts/utilities/color.py | 115 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 bot/exts/utilities/color.py (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py new file mode 100644 index 00000000..dd922bf9 --- /dev/null +++ b/bot/exts/utilities/color.py @@ -0,0 +1,115 @@ +# imports +import colorsys +import logging + +import PIL +from discord import Embed +from discord.ext import commands +from rapidfuzz import process + +from bot.bot import Bot +from bot.constants import Colours + +# Planning to use discord-flags, hence require changes to poetry.lock file +# from discord.ext import flags + +logger = logging.getLogger(__name__) + +# constants if needed +# Color URLs - will be replaced by JSON file? +COLOR_JSON_PATH = ".bot//exts//resources//evergreen//" +COLOR_URL_XKCD = "https://xkcd.com/color/rgb/" +COLOR_URL_NAME_THAT_COLOR = "https://github.com/ryanzec/name-that-color/blob/master/lib/ntc.js#L116-L1681" + + +COLOR_ERROR = Embed( + title="Input color is not possible", + description="The color code {user_color} is not a possible color combination." + "\nThe range of possible values are: " + "\nRGB & HSV: 0-255" + "\nCMYK: 0-100%" + "\nHSL: 0-360 degrees" + "\nHex: #000000-#FFFFFF" +) +COLOR_EMBED = Embed( + title="{color_name}", + description="RGB" + "\n{RGB}" + "\nHSV" + "\n{HSV}" + "\nCMYK" + "\n{CMYK}" + "\nHSL" + "\n{HSL}" + "\nHex" + "\n{Hex}" +) + + +# define color command +class Color(commands.cog): + """User initiated command to receive color information.""" + + def __init__(self, bot: Bot): + self.bot = bot + + # ? possible to use discord-flags to allow user to decide on color + # https://pypi.org/project/discord-flags/ + # @flags.add_flag("--rgb", type=str) + # @flags.add_flag("--hsv", type=str) + # @flags.add_flag("--cmyk", type=str) + # @flags.add_flag("--hsl", type=str) + # @flags.add_flag("--hex", type=str) + # @flags.add_flag("--name", type=str) + # @flags.command() + @commands.command(aliases=["color", "colour"]) + @commands.cooldown(1, 10, commands.cooldowns.BucketType.user) + async def color(self, ctx: commands.Context, *, user_color: str) -> None: + """Send information on input color code or color name.""" + # need to check if user_color is RGB, HSV, CMYK, HSL, Hex or color name + # should we assume the color is RGB if not defined? + # should discord tags be used? + # need to review discord.py V2.0 + + # TODO code to check if color code is possible + await ctx.send(embed=COLOR_ERROR.format(color=user_color)) + # await ctx.send(embed=COLOR_EMBED.format( + # RGB=color_dict["RGB"], + # HSV=color_dict["HSV"], + # HSL=color_dict["HSL"], + # CMYK=color_dict["CMYK"], + # HSL=color_dict["HSL"], + # Hex=color_dict["Hex"], + # color_name=color_dict["color_name"] + # ).set_image() # url for image? + # ) + + # TODO pass for now + pass + + # if user_color in color_lists: + # # TODO fuzzy match for color + # pass + + async def color_converter(self, color: str, code_type: str) -> dict: + """Generate alternative color codes for use in the embed.""" + # TODO add code to take color and code type and return other types + # color_dict = { + # "RGB": color_RGB, + # "HSV": color_HSV, + # "HSL": color_HSL, + # "CMYK": color_CMYK, + # "HSL": color_HSL, + # "Hex": color_Hex, + # "color_name": color_name, + # } + pass + + async def photo_generator(self, color: str) -> None: + """Generate photo to use in embed.""" + # TODO need to find a way to store photo in cache to add to embed, then remove + + +def setup(bot: Bot) -> None: + """Load the Color Cog.""" + bot.add_cog(Color(bot)) -- cgit v1.2.3 From 37c4d55275ed1ceadc86ad2c437a6dd337614bea Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Mon, 6 Sep 2021 20:13:17 -0400 Subject: Continue work in progress Implemented the thumbnail creation from CyberCitizen0, worked on adding some features to the program. Notable Changes: -Check if user passes in hex color -Create thumbnail based on rgb_color To-Do: -Create hex color from rgb color -Create readable rgb color from user input Co-authored-by: Mohammad Rafivulla <77384412+CyberCitizen01@users.noreply.github.com> --- bot/exts/utilities/color.py | 140 +++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 79 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index dd922bf9..1a4f7031 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,113 +1,95 @@ # imports import colorsys import logging +import re +from io import BytesIO -import PIL -from discord import Embed +from PIL import Image, ImageColor +from discord import Embed, File from discord.ext import commands from rapidfuzz import process from bot.bot import Bot from bot.constants import Colours -# Planning to use discord-flags, hence require changes to poetry.lock file -# from discord.ext import flags logger = logging.getLogger(__name__) -# constants if needed -# Color URLs - will be replaced by JSON file? -COLOR_JSON_PATH = ".bot//exts//resources//evergreen//" -COLOR_URL_XKCD = "https://xkcd.com/color/rgb/" -COLOR_URL_NAME_THAT_COLOR = "https://github.com/ryanzec/name-that-color/blob/master/lib/ntc.js#L116-L1681" - - -COLOR_ERROR = Embed( - title="Input color is not possible", - description="The color code {user_color} is not a possible color combination." - "\nThe range of possible values are: " - "\nRGB & HSV: 0-255" - "\nCMYK: 0-100%" - "\nHSL: 0-360 degrees" - "\nHex: #000000-#FFFFFF" -) -COLOR_EMBED = Embed( - title="{color_name}", - description="RGB" - "\n{RGB}" - "\nHSV" - "\n{HSV}" - "\nCMYK" - "\n{CMYK}" - "\nHSL" - "\n{HSL}" - "\nHex" - "\n{Hex}" -) + +ERROR_MSG = """The color code {user_color} is not a possible color combination. +\nThe range of possible values are: +\nRGB & HSV: 0-255 +\nCMYK: 0-100% +\nHSL: 0-360 degrees +\nHex: #000000-#FFFFFF +""" # define color command -class Color(commands.cog): +class Color(commands.Cog): """User initiated command to receive color information.""" def __init__(self, bot: Bot): self.bot = bot - # ? possible to use discord-flags to allow user to decide on color - # https://pypi.org/project/discord-flags/ - # @flags.add_flag("--rgb", type=str) - # @flags.add_flag("--hsv", type=str) - # @flags.add_flag("--cmyk", type=str) - # @flags.add_flag("--hsl", type=str) - # @flags.add_flag("--hex", type=str) - # @flags.add_flag("--name", type=str) - # @flags.command() - @commands.command(aliases=["color", "colour"]) + @commands.command(aliases=["colour"]) @commands.cooldown(1, 10, commands.cooldowns.BucketType.user) async def color(self, ctx: commands.Context, *, user_color: str) -> None: """Send information on input color code or color name.""" # need to check if user_color is RGB, HSV, CMYK, HSL, Hex or color name # should we assume the color is RGB if not defined? - # should discord tags be used? - # need to review discord.py V2.0 - - # TODO code to check if color code is possible - await ctx.send(embed=COLOR_ERROR.format(color=user_color)) - # await ctx.send(embed=COLOR_EMBED.format( - # RGB=color_dict["RGB"], - # HSV=color_dict["HSV"], - # HSL=color_dict["HSL"], - # CMYK=color_dict["CMYK"], - # HSL=color_dict["HSL"], - # Hex=color_dict["Hex"], - # color_name=color_dict["color_name"] - # ).set_image() # url for image? - # ) - - # TODO pass for now - pass + + if "#" in user_color: + logger.info(f"{user_color = }") + hex_match = re.search(r"^#(?:[0-9a-fA-F]{3}){1,2}$", user_color) + if hex_match: + hex_color = int(hex(int(user_color.replace("#", ""), 16)), 0) + logger.info(f"{hex_color = }") + rgb_color = ImageColor.getcolor(user_color, "RGB") + else: + await ctx.send(embed=Embed( + title="An error has occured.", + description=ERROR_MSG.format(user_color=user_color) + ) + ) + + elif "RGB" or "rgb" in user_color: + rgb_parse = user_color.split() + rgb = rgb_parse[1:].replace(", ", "") + logger.info(f"{rgb = }") + logger.info(f"{rgb[0] = }") + logger.info(f"{rgb[1] = }") + logger.info(f"{rgb[2] = }") + rgb_color = tuple(rgb) + hex_color = f"0x{int(rgb[0]):02x}{int(rgb[1]):02x}{int(rgb[2]):02x}" + + main_embed = Embed( + title=user_color, + color=hex_color, + ) + async with ctx.typing(): + file = await self._create_thumbnail_attachment(rgb_color) + main_embed.set_thumbnail(url="attachment://color.png") + + await ctx.send(file=file, embed=main_embed) + + async def _create_thumbnail_attachment(self, color: str) -> File: + """Generate a thumbnail from `color`.""" + + thumbnail = Image.new("RGB", (100, 100), color=color) + bufferedio = BytesIO() + thumbnail.save(bufferedio, format="PNG") + bufferedio.seek(0) + + file = File(bufferedio, filename="color.png") + + return file + # if user_color in color_lists: # # TODO fuzzy match for color # pass - async def color_converter(self, color: str, code_type: str) -> dict: - """Generate alternative color codes for use in the embed.""" - # TODO add code to take color and code type and return other types - # color_dict = { - # "RGB": color_RGB, - # "HSV": color_HSV, - # "HSL": color_HSL, - # "CMYK": color_CMYK, - # "HSL": color_HSL, - # "Hex": color_Hex, - # "color_name": color_name, - # } - pass - - async def photo_generator(self, color: str) -> None: - """Generate photo to use in embed.""" - # TODO need to find a way to store photo in cache to add to embed, then remove def setup(bot: Bot) -> None: -- cgit v1.2.3 From a16ee6ecd1b1449e8203e650495809e85ea9ddc8 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Tue, 7 Sep 2021 07:39:21 -0400 Subject: Fixing flake8 errors, code style Still a work in progress but commenting out stub code and unused imports. List of To-Do's still applies. --- bot/exts/utilities/color.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 1a4f7031..b1a77b28 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,5 +1,5 @@ # imports -import colorsys +# import colorsys import logging import re from io import BytesIO @@ -7,10 +7,10 @@ from io import BytesIO from PIL import Image, ImageColor from discord import Embed, File from discord.ext import commands -from rapidfuzz import process +# from rapidfuzz import process from bot.bot import Bot -from bot.constants import Colours +# from bot.constants import Colours logger = logging.getLogger(__name__) @@ -46,12 +46,14 @@ class Color(commands.Cog): hex_color = int(hex(int(user_color.replace("#", ""), 16)), 0) logger.info(f"{hex_color = }") rgb_color = ImageColor.getcolor(user_color, "RGB") + logger.info(f"{rgb_color = }") else: - await ctx.send(embed=Embed( + await ctx.send( + embed=Embed( title="An error has occured.", - description=ERROR_MSG.format(user_color=user_color) - ) + description=ERROR_MSG.format(user_color=user_color), ) + ) elif "RGB" or "rgb" in user_color: rgb_parse = user_color.split() @@ -64,7 +66,7 @@ class Color(commands.Cog): hex_color = f"0x{int(rgb[0]):02x}{int(rgb[1]):02x}{int(rgb[2]):02x}" main_embed = Embed( - title=user_color, + title=user_color, # need to replace with fuzzymatch color name color=hex_color, ) async with ctx.typing(): @@ -75,7 +77,6 @@ class Color(commands.Cog): async def _create_thumbnail_attachment(self, color: str) -> File: """Generate a thumbnail from `color`.""" - thumbnail = Image.new("RGB", (100, 100), color=color) bufferedio = BytesIO() thumbnail.save(bufferedio, format="PNG") @@ -85,11 +86,8 @@ class Color(commands.Cog): return file - # if user_color in color_lists: - # # TODO fuzzy match for color - # pass - + # # fuzzy match for color def setup(bot: Bot) -> None: -- cgit v1.2.3 From 86cde23bcec5090e73abaf1289641d299f2e8677 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Tue, 7 Sep 2021 07:48:07 -0400 Subject: Add embed fields for Hex and RGB --- bot/exts/utilities/color.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index b1a77b28..b1b423e7 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -73,6 +73,16 @@ class Color(commands.Cog): file = await self._create_thumbnail_attachment(rgb_color) main_embed.set_thumbnail(url="attachment://color.png") + main_embed.add_field( + name="Hex", + value=f">>Hex #{hex_color}", + inline=False, + ) + main_embed.add_field( + name="RGB", + value=f">>RGB {rgb_color}", + inline=False, + ) await ctx.send(file=file, embed=main_embed) async def _create_thumbnail_attachment(self, color: str) -> File: -- cgit v1.2.3 From 92b63e85501be8169aa3be71d8f58a5ca69d8572 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Tue, 7 Sep 2021 12:51:45 -0400 Subject: Update code to use 'mode' variable Updated the code to parse user_input depending on the color code 'mode' passed to the command. Added stub code for future color codes and embeds if mode is None or wrong code. --- bot/exts/utilities/color.py | 59 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 13 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index b1b423e7..1efacead 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -10,7 +10,7 @@ from discord.ext import commands # from rapidfuzz import process from bot.bot import Bot -# from bot.constants import Colours +from bot.constants import Colours logger = logging.getLogger(__name__) @@ -33,13 +33,12 @@ class Color(commands.Cog): self.bot = bot @commands.command(aliases=["colour"]) - @commands.cooldown(1, 10, commands.cooldowns.BucketType.user) - async def color(self, ctx: commands.Context, *, user_color: str) -> None: + async def color(self, ctx: commands.Context, mode: str, user_color: str) -> None: """Send information on input color code or color name.""" # need to check if user_color is RGB, HSV, CMYK, HSL, Hex or color name # should we assume the color is RGB if not defined? - if "#" in user_color: + if mode.lower() == "hex": logger.info(f"{user_color = }") hex_match = re.search(r"^#(?:[0-9a-fA-F]{3}){1,2}$", user_color) if hex_match: @@ -55,15 +54,32 @@ class Color(commands.Cog): ) ) - elif "RGB" or "rgb" in user_color: - rgb_parse = user_color.split() - rgb = rgb_parse[1:].replace(", ", "") - logger.info(f"{rgb = }") - logger.info(f"{rgb[0] = }") - logger.info(f"{rgb[1] = }") - logger.info(f"{rgb[2] = }") - rgb_color = tuple(rgb) - hex_color = f"0x{int(rgb[0]):02x}{int(rgb[1]):02x}{int(rgb[2]):02x}" + elif mode.lower() == "rgb": + logger.info(f"{user_color = }") + # rgb_color = user_color + + elif mode.lower() == "hsv": + pass + elif mode.lower() == "hsl": + pass + elif mode.lower() == "cmyk": + pass + else: + # mode is either None or an invalid code + if mode is None: + no_mode_embed = Embed( + title="No 'mode' was passed, please define a color code.", + color=Colours.soft_red, + ) + await ctx.send(embed=no_mode_embed) + return + wrong_mode_embed = Embed( + title=f"The color code {mode} is not a valid option", + description="Possible modes are: Hex, RGB, HSV, HSL and CMYK.", + color=Colours.soft_red, + ) + await ctx.send(embed=wrong_mode_embed) + return main_embed = Embed( title=user_color, # need to replace with fuzzymatch color name @@ -83,6 +99,23 @@ class Color(commands.Cog): value=f">>RGB {rgb_color}", inline=False, ) + """ + main_embed.add_field( + name="HSV", + value=f">>HSV {hsv_color}", + inline=False, + ) + main_embed.add_field( + name="HSL", + value=f">>HSL {hsl_color}", + inline=False, + ) + main_embed.add_field( + name="CMYK", + value=f">>CMYK {cmyk_color}", + inline=False, + ) + """ await ctx.send(file=file, embed=main_embed) async def _create_thumbnail_attachment(self, color: str) -> File: -- cgit v1.2.3 From 348de13c97baac9813ad6be16a49cffa5c536751 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 8 Sep 2021 10:06:47 -0400 Subject: Minor fixes --- bot/exts/utilities/color.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 1efacead..d7fff503 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,4 +1,3 @@ -# imports # import colorsys import logging import re @@ -35,16 +34,14 @@ class Color(commands.Cog): @commands.command(aliases=["colour"]) async def color(self, ctx: commands.Context, mode: str, user_color: str) -> None: """Send information on input color code or color name.""" - # need to check if user_color is RGB, HSV, CMYK, HSL, Hex or color name - # should we assume the color is RGB if not defined? - + logger.info(f"{mode = }") + logger.info(f"{user_color = }") if mode.lower() == "hex": - logger.info(f"{user_color = }") hex_match = re.search(r"^#(?:[0-9a-fA-F]{3}){1,2}$", user_color) if hex_match: hex_color = int(hex(int(user_color.replace("#", ""), 16)), 0) - logger.info(f"{hex_color = }") rgb_color = ImageColor.getcolor(user_color, "RGB") + logger.info(f"{hex_color = }") logger.info(f"{rgb_color = }") else: await ctx.send( @@ -53,11 +50,8 @@ class Color(commands.Cog): description=ERROR_MSG.format(user_color=user_color), ) ) - elif mode.lower() == "rgb": - logger.info(f"{user_color = }") - # rgb_color = user_color - + pass elif mode.lower() == "hsv": pass elif mode.lower() == "hsl": @@ -66,6 +60,7 @@ class Color(commands.Cog): pass else: # mode is either None or an invalid code + # need to handle whether user passes color name if mode is None: no_mode_embed = Embed( title="No 'mode' was passed, please define a color code.", -- cgit v1.2.3 From 39d10a42d592da3eb8a4f35111c7a6e815ab7bd8 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Fri, 10 Sep 2021 07:47:12 -0400 Subject: Test to capture all user_input --- bot/exts/utilities/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index d7fff503..c4df3e10 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -32,7 +32,7 @@ class Color(commands.Cog): self.bot = bot @commands.command(aliases=["colour"]) - async def color(self, ctx: commands.Context, mode: str, user_color: str) -> None: + async def color(self, ctx: commands.Context, mode: str, *, user_color: str) -> None: """Send information on input color code or color name.""" logger.info(f"{mode = }") logger.info(f"{user_color = }") -- cgit v1.2.3 From 854452c60c589fa414b8cac66edcc884caa03bd6 Mon Sep 17 00:00:00 2001 From: CyberCitizen01 Date: Sat, 11 Sep 2021 04:17:18 +0530 Subject: Added "colour information" and "colour conversion" features Details: https://github.com/python-discord/sir-lancebot/issues/677 NOTE: get_color_fields (line 122) method explicity requires a valid tuple of RGB values. --- bot/exts/utilities/color.py | 134 +++++++++++++++++++++++++++++++++----------- 1 file changed, 100 insertions(+), 34 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index c4df3e10..6abfc006 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,4 +1,4 @@ -# import colorsys +import colorsys import logging import re from io import BytesIO @@ -76,44 +76,28 @@ class Color(commands.Cog): await ctx.send(embed=wrong_mode_embed) return - main_embed = Embed( - title=user_color, # need to replace with fuzzymatch color name - color=hex_color, - ) async with ctx.typing(): - file = await self._create_thumbnail_attachment(rgb_color) + main_embed = Embed( + title=user_color, # need to replace with fuzzymatch color name + description='(Approx..)', + color=hex_color, + ) + + file = await self.create_thumbnail_attachment(rgb_color) main_embed.set_thumbnail(url="attachment://color.png") - main_embed.add_field( - name="Hex", - value=f">>Hex #{hex_color}", - inline=False, - ) - main_embed.add_field( - name="RGB", - value=f">>RGB {rgb_color}", - inline=False, - ) - """ - main_embed.add_field( - name="HSV", - value=f">>HSV {hsv_color}", - inline=False, - ) - main_embed.add_field( - name="HSL", - value=f">>HSL {hsl_color}", - inline=False, - ) - main_embed.add_field( - name="CMYK", - value=f">>CMYK {cmyk_color}", - inline=False, - ) - """ + fields = self.get_color_fields(rgb_color) + for field in fields: + main_embed.add_field( + name=field['name'], + value=field['value'], + inline=False, + ) + await ctx.send(file=file, embed=main_embed) - async def _create_thumbnail_attachment(self, color: str) -> File: + @staticmethod + async def create_thumbnail_attachment(color: str) -> File: """Generate a thumbnail from `color`.""" thumbnail = Image.new("RGB", (100, 100), color=color) bufferedio = BytesIO() @@ -124,6 +108,88 @@ class Color(commands.Cog): return file + @staticmethod + def get_color_fields(rgb_color: tuple[int, int, int]) -> list[dict]: + """Converts from `RGB` to `CMYK`, `HSV`, `HSL` and returns a list of fields.""" + + def _rgb_to_hex(rgb_color: tuple[int, int, int]) -> str: + """To convert from `RGB` to `Hex` notation.""" + return '#' + ''.join(hex(color)[2:].zfill(2) for color in rgb_color).upper() + + def _rgb_to_cmyk(rgb_color: tuple[int, int, int]) -> tuple[int, int, int, int]: + """To convert from `RGB` to `CMYK` color space.""" + r, g, b = rgb_color + + # RGB_SCALE -> 255 + # CMYK_SCALE -> 100 + + if (r == g == b == 0): + return 0, 0, 0, 100 # Representing Black + + # rgb [0,RGB_SCALE] -> cmy [0,1] + c = 1 - r / 255 + m = 1 - g / 255 + y = 1 - b / 255 + + # extract out k [0, 1] + min_cmy = min(c, m, y) + c = (c - min_cmy) / (1 - min_cmy) + m = (m - min_cmy) / (1 - min_cmy) + y = (y - min_cmy) / (1 - min_cmy) + k = min_cmy + + # rescale to the range [0,CMYK_SCALE] and round off + c = round(c * 100) + m = round(m * 100) + y = round(y * 100) + k = round(k * 100) + + return c, m, y, k + + def _rgb_to_hsv(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: + """To convert from `RGB` to `HSV` color space.""" + r, g, b = rgb_color + h, v, s = colorsys.rgb_to_hsv(r/float(255), g/float(255), b/float(255)) + h = round(h*360) + s = round(s*100) + v = round(v*100) + return h, s, v + + def _rgb_to_hsl(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: + """To convert from `RGB` to `HSL` color space.""" + r, g, b = rgb_color + h, l, s = colorsys.rgb_to_hls(r/float(255), g/float(255), b/float(255)) + h = round(h*360) + s = round(s*100) + l = round(l*100) # noqa: E741 It's little `L`, Reason: To maintain consistency. + return h, s, l + + hex_color = _rgb_to_hex(rgb_color) + cmyk_color = _rgb_to_cmyk(rgb_color) + hsv_color = _rgb_to_hsv(rgb_color) + hsl_color = _rgb_to_hsl(rgb_color) + + all_fields = [ + { + "name": "RGB", + "value": f"» rgb {rgb_color}\n» hex {hex_color}" + }, + { + "name": "CMYK", + "value": f"» cmyk {cmyk_color}" + }, + { + "name": "HSV", + "value": f"» hsv {hsv_color}" + }, + { + "name": "HSL", + "value": f"» hsl {hsl_color}" + }, + ] + + return all_fields + # if user_color in color_lists: # # fuzzy match for color -- cgit v1.2.3 From 172fd3389481cc690d2eedaf1f70c56efb018bcf Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sat, 11 Sep 2021 09:57:13 -0400 Subject: Add fuzzy match function I made a few changes, the biggest being the fuzzy match function to return a hex color code based on an input color name. Open items that I can think of so far: -Since the json file has color names and hex values, in order to use fuzzy matching for a color name the color must first be converted to hex. Currently there is only a rgb to anything function which returns values in a dictionary. -The main embed creation references the rgb_color before it is defined, should the command function be moved to the bottom of the file or just the main embed creation and sending? -When using the rgb mode, should the user be forced to do (r, g, b) or should the command handle an input of "r, g, b"? If you are reading this, thank you. --- bot/exts/utilities/color.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 6abfc006..9e199dd4 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,4 +1,5 @@ import colorsys +import json import logging import re from io import BytesIO @@ -6,7 +7,7 @@ from io import BytesIO from PIL import Image, ImageColor from discord import Embed, File from discord.ext import commands -# from rapidfuzz import process +from rapidfuzz import process from bot.bot import Bot from bot.constants import Colours @@ -23,6 +24,8 @@ ERROR_MSG = """The color code {user_color} is not a possible color combination. \nHex: #000000-#FFFFFF """ +COLOR_LIST = "bot/resources/utilities/ryanzec_colours.json" + # define color command class Color(commands.Cog): @@ -46,7 +49,7 @@ class Color(commands.Cog): else: await ctx.send( embed=Embed( - title="An error has occured.", + title="There was an issue converting the hex color code.", description=ERROR_MSG.format(user_color=user_color), ) ) @@ -58,9 +61,10 @@ class Color(commands.Cog): pass elif mode.lower() == "cmyk": pass + elif mode.lower() == "name": + color_name, hex_color = self.match_color(user_color) else: # mode is either None or an invalid code - # need to handle whether user passes color name if mode is None: no_mode_embed = Embed( title="No 'mode' was passed, please define a color code.", @@ -70,7 +74,7 @@ class Color(commands.Cog): return wrong_mode_embed = Embed( title=f"The color code {mode} is not a valid option", - description="Possible modes are: Hex, RGB, HSV, HSL and CMYK.", + description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.", color=Colours.soft_red, ) await ctx.send(embed=wrong_mode_embed) @@ -78,15 +82,15 @@ class Color(commands.Cog): async with ctx.typing(): main_embed = Embed( - title=user_color, # need to replace with fuzzymatch color name + title=color_name, description='(Approx..)', - color=hex_color, + color=rgb_color, ) file = await self.create_thumbnail_attachment(rgb_color) main_embed.set_thumbnail(url="attachment://color.png") - fields = self.get_color_fields(rgb_color) + for field in fields: main_embed.add_field( name=field['name'], @@ -94,7 +98,7 @@ class Color(commands.Cog): inline=False, ) - await ctx.send(file=file, embed=main_embed) + await ctx.send(file=file, embed=main_embed) @staticmethod async def create_thumbnail_attachment(color: str) -> File: @@ -192,6 +196,17 @@ class Color(commands.Cog): # if user_color in color_lists: # # fuzzy match for color + @staticmethod + def match_color(user_color: str) -> str: + """Use fuzzy matching to return a hex color code based on the user's input.""" + with open(COLOR_LIST) as f: + color_list = json.load(f) + logger.debug(f"{type(color_list) = }") + match, certainty, _ = process.extractOne(query=user_color, choices=color_list.keys(), score_cutoff=50) + logger.debug(f"{match = }, {certainty = }") + hex_match = color_list[match] + logger.debug(f"{hex_match = }") + return match, hex_match def setup(bot: Bot) -> None: -- cgit v1.2.3 From e77a8319e7e681262c73bdbb1335a708de676e8a Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sat, 11 Sep 2021 10:09:27 -0400 Subject: Remove placeholder comment --- bot/exts/utilities/color.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 9e199dd4..6452d292 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -194,8 +194,6 @@ class Color(commands.Cog): return all_fields - # if user_color in color_lists: - # # fuzzy match for color @staticmethod def match_color(user_color: str) -> str: """Use fuzzy matching to return a hex color code based on the user's input.""" -- cgit v1.2.3 From 68c0de59f6f31fb1123c7ed3468c4faac202d9f6 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 12 Sep 2021 08:39:47 -0400 Subject: Load json file once --- bot/exts/utilities/color.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 6452d292..dd470197 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -25,6 +25,8 @@ ERROR_MSG = """The color code {user_color} is not a possible color combination. """ COLOR_LIST = "bot/resources/utilities/ryanzec_colours.json" +with open(COLOR_LIST) as f: + COLOR_JSON = json.load(f) # define color command @@ -197,12 +199,9 @@ class Color(commands.Cog): @staticmethod def match_color(user_color: str) -> str: """Use fuzzy matching to return a hex color code based on the user's input.""" - with open(COLOR_LIST) as f: - color_list = json.load(f) - logger.debug(f"{type(color_list) = }") - match, certainty, _ = process.extractOne(query=user_color, choices=color_list.keys(), score_cutoff=50) + match, certainty, _ = process.extractOne(query=user_color, choices=COLOR_JSON.keys(), score_cutoff=50) logger.debug(f"{match = }, {certainty = }") - hex_match = color_list[match] + hex_match = COLOR_JSON[match] logger.debug(f"{hex_match = }") return match, hex_match -- cgit v1.2.3 From 674221ead6d5376b22a1ea31bcec0cbadecd6104 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 15 Sep 2021 16:16:52 -0400 Subject: Fix Flake8 spacing errors --- bot/exts/utilities/color.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index dd470197..eb9d5f4d 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -155,19 +155,19 @@ class Color(commands.Cog): def _rgb_to_hsv(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: """To convert from `RGB` to `HSV` color space.""" r, g, b = rgb_color - h, v, s = colorsys.rgb_to_hsv(r/float(255), g/float(255), b/float(255)) - h = round(h*360) - s = round(s*100) - v = round(v*100) + h, v, s = colorsys.rgb_to_hsv(r / float(255), g / float(255), b / float(255)) + h = round(h * 360) + s = round(s * 100) + v = round(v * 100) return h, s, v def _rgb_to_hsl(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: """To convert from `RGB` to `HSL` color space.""" r, g, b = rgb_color - h, l, s = colorsys.rgb_to_hls(r/float(255), g/float(255), b/float(255)) - h = round(h*360) - s = round(s*100) - l = round(l*100) # noqa: E741 It's little `L`, Reason: To maintain consistency. + h, l, s = colorsys.rgb_to_hls(r / float(255), g / float(255), b / float(255)) + h = round(h * 360) + s = round(s * 100) + l = round(l * 100) # noqa: E741 It's little `L`, Reason: To maintain consistency. return h, s, l hex_color = _rgb_to_hex(rgb_color) -- cgit v1.2.3 From 029b8052b3d038be2808d5995d4f6422158b0084 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 19 Sep 2021 08:20:18 -0400 Subject: Reword json file variables and mapping --- bot/exts/utilities/color.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index eb9d5f4d..94c9d337 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -24,9 +24,9 @@ ERROR_MSG = """The color code {user_color} is not a possible color combination. \nHex: #000000-#FFFFFF """ -COLOR_LIST = "bot/resources/utilities/ryanzec_colours.json" -with open(COLOR_LIST) as f: - COLOR_JSON = json.load(f) +COLOR_JSON_PATH = "bot/resources/utilities/ryanzec_colours.json" +with open(COLOR_JSON_PATH) as f: + COLOR_MAPPING = json.load(f) # define color command @@ -199,9 +199,9 @@ class Color(commands.Cog): @staticmethod def match_color(user_color: str) -> str: """Use fuzzy matching to return a hex color code based on the user's input.""" - match, certainty, _ = process.extractOne(query=user_color, choices=COLOR_JSON.keys(), score_cutoff=50) + match, certainty, _ = process.extractOne(query=user_color, choices=COLOR_MAPPING.keys(), score_cutoff=50) logger.debug(f"{match = }, {certainty = }") - hex_match = COLOR_JSON[match] + hex_match = COLOR_MAPPING[match] logger.debug(f"{hex_match = }") return match, hex_match -- cgit v1.2.3 From 40e04bf4d304f8b2f231fedcb38db1f532550f0c Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 19 Sep 2021 10:20:58 -0400 Subject: Continue work on hex and rgb color commands --- bot/exts/utilities/color.py | 87 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 18 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 94c9d337..2bec6ba3 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -31,23 +31,45 @@ with open(COLOR_JSON_PATH) as f: # define color command class Color(commands.Cog): - """User initiated command to receive color information.""" + """User initiated commands to receive color information.""" def __init__(self, bot: Bot): self.bot = bot @commands.command(aliases=["colour"]) async def color(self, ctx: commands.Context, mode: str, *, user_color: str) -> None: - """Send information on input color code or color name.""" - logger.info(f"{mode = }") - logger.info(f"{user_color = }") + """ + Send information on input color code or color name. + + Possible modes are: "hex", "rgb", "hsv", "hsl", "cmyk" or "name". + """ + logger.debug(f"{mode = }") + logger.debug(f"{user_color = }") if mode.lower() == "hex": - hex_match = re.search(r"^#(?:[0-9a-fA-F]{3}){1,2}$", user_color) + hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", user_color) if hex_match: hex_color = int(hex(int(user_color.replace("#", ""), 16)), 0) - rgb_color = ImageColor.getcolor(user_color, "RGB") - logger.info(f"{hex_color = }") - logger.info(f"{rgb_color = }") + if "#" in user_color: + rgb_color = ImageColor.getcolor(user_color, "RGB") + elif "0x" in user_color: + hex_ = user_color.replace("0x", "#") + rgb_color = ImageColor.getcolor(hex_, "RGB") + else: + hex_ = "#" + user_color + rgb_color = ImageColor.getcolor(hex_, "RGB") + (r, g, b) = rgb_color + discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) + all_colors = self.get_color_fields(rgb_color) + hex_color = all_colors[1]["value"].replace("» hex ", "") + cmyk_color = all_colors[2]["value"].replace("» cmyk ", "") + hsv_color = all_colors[3]["value"].replace("» hsv ", "") + hsl_color = all_colors[4]["value"].replace("» hsl ", "") + logger.debug(f"{rgb_color = }") + logger.debug(f"{hex_color = }") + logger.debug(f"{hsv_color = }") + logger.debug(f"{hsl_color = }") + logger.debug(f"{cmyk_color = }") + color_name, _ = self.match_color(hex_color) else: await ctx.send( embed=Embed( @@ -56,7 +78,22 @@ class Color(commands.Cog): ) ) elif mode.lower() == "rgb": - pass + if "(" in user_color: + remove = "[() ]" + rgb_color = re.sub(remove, "", user_color) + rgb_color = tuple(map(int, rgb_color.split(","))) + elif "," in user_color: + rgb_color = tuple(map(int, user_color.split(","))) + else: + rgb_color = tuple(map(int, user_color.split(" "))) + (r, g, b) = rgb_color + discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) + all_colors = self.get_color_fields(rgb_color) + hex_color = all_colors[1]["value"].replace("» hex ", "") + cmyk_color = all_colors[2]["value"].replace("» cmyk ", "") + hsv_color = all_colors[3]["value"].replace("» hsv ", "") + hsl_color = all_colors[4]["value"].replace("» hsl ", "") + color_name, _ = self.match_color(hex_color) elif mode.lower() == "hsv": pass elif mode.lower() == "hsl": @@ -70,6 +107,7 @@ class Color(commands.Cog): if mode is None: no_mode_embed = Embed( title="No 'mode' was passed, please define a color code.", + description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.", color=Colours.soft_red, ) await ctx.send(embed=no_mode_embed) @@ -86,7 +124,7 @@ class Color(commands.Cog): main_embed = Embed( title=color_name, description='(Approx..)', - color=rgb_color, + color=discord_rgb_int, ) file = await self.create_thumbnail_attachment(rgb_color) @@ -120,7 +158,7 @@ class Color(commands.Cog): def _rgb_to_hex(rgb_color: tuple[int, int, int]) -> str: """To convert from `RGB` to `Hex` notation.""" - return '#' + ''.join(hex(color)[2:].zfill(2) for color in rgb_color).upper() + return '#' + ''.join(hex(int(color))[2:].zfill(2) for color in rgb_color).upper() def _rgb_to_cmyk(rgb_color: tuple[int, int, int]) -> tuple[int, int, int, int]: """To convert from `RGB` to `CMYK` color space.""" @@ -178,7 +216,11 @@ class Color(commands.Cog): all_fields = [ { "name": "RGB", - "value": f"» rgb {rgb_color}\n» hex {hex_color}" + "value": f"» rgb {rgb_color}" + }, + { + "name": "HEX", + "value": f"» hex {hex_color}" }, { "name": "CMYK", @@ -197,13 +239,22 @@ class Color(commands.Cog): return all_fields @staticmethod - def match_color(user_color: str) -> str: + def match_color(input_hex_color: str) -> str: """Use fuzzy matching to return a hex color code based on the user's input.""" - match, certainty, _ = process.extractOne(query=user_color, choices=COLOR_MAPPING.keys(), score_cutoff=50) - logger.debug(f"{match = }, {certainty = }") - hex_match = COLOR_MAPPING[match] - logger.debug(f"{hex_match = }") - return match, hex_match + try: + match, certainty, _ = process.extractOne( + query=input_hex_color, + choices=COLOR_MAPPING.keys(), + score_cutoff=50 + ) + logger.debug(f"{match = }, {certainty = }") + hex_match = COLOR_MAPPING[match] + logger.debug(f"{hex_match = }") + return match, hex_match + except TypeError: + match = "No color name match found." + hex_match = input_hex_color + return match, hex_match def setup(bot: Bot) -> None: -- cgit v1.2.3 From d55b59e5590e701094915bb1aff36d0cd38d061b Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 19 Sep 2021 17:49:32 -0400 Subject: Add all color modes and name matching --- bot/exts/utilities/color.py | 140 ++++++++++++++++++++++++++++++++------------ 1 file changed, 104 insertions(+), 36 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 2bec6ba3..9b1f3776 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -45,6 +45,7 @@ class Color(commands.Cog): """ logger.debug(f"{mode = }") logger.debug(f"{user_color = }") + color_name = None if mode.lower() == "hex": hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", user_color) if hex_match: @@ -57,19 +58,6 @@ class Color(commands.Cog): else: hex_ = "#" + user_color rgb_color = ImageColor.getcolor(hex_, "RGB") - (r, g, b) = rgb_color - discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) - all_colors = self.get_color_fields(rgb_color) - hex_color = all_colors[1]["value"].replace("» hex ", "") - cmyk_color = all_colors[2]["value"].replace("» cmyk ", "") - hsv_color = all_colors[3]["value"].replace("» hsv ", "") - hsl_color = all_colors[4]["value"].replace("» hsl ", "") - logger.debug(f"{rgb_color = }") - logger.debug(f"{hex_color = }") - logger.debug(f"{hsv_color = }") - logger.debug(f"{hsl_color = }") - logger.debug(f"{cmyk_color = }") - color_name, _ = self.match_color(hex_color) else: await ctx.send( embed=Embed( @@ -78,30 +66,22 @@ class Color(commands.Cog): ) ) elif mode.lower() == "rgb": - if "(" in user_color: - remove = "[() ]" - rgb_color = re.sub(remove, "", user_color) - rgb_color = tuple(map(int, rgb_color.split(","))) - elif "," in user_color: - rgb_color = tuple(map(int, user_color.split(","))) - else: - rgb_color = tuple(map(int, user_color.split(" "))) - (r, g, b) = rgb_color - discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) - all_colors = self.get_color_fields(rgb_color) - hex_color = all_colors[1]["value"].replace("» hex ", "") - cmyk_color = all_colors[2]["value"].replace("» cmyk ", "") - hsv_color = all_colors[3]["value"].replace("» hsv ", "") - hsl_color = all_colors[4]["value"].replace("» hsl ", "") - color_name, _ = self.match_color(hex_color) + rgb_color = self.tuple_create(user_color) elif mode.lower() == "hsv": - pass + hsv_temp = self.tuple_create(user_color) + rgb_color = self.hsv_to_rgb(hsv_temp) elif mode.lower() == "hsl": - pass + hsl_temp = self.tuple_create(user_color) + rgb_color = self.hsl_to_rgb(hsl_temp) elif mode.lower() == "cmyk": - pass + cmyk_temp = self.tuple_create(user_color) + rgb_color = self.cmyk_to_rgb(cmyk_temp) elif mode.lower() == "name": - color_name, hex_color = self.match_color(user_color) + color_name, hex_color = self.match_color_name(user_color) + if "#" in hex_color: + rgb_color = ImageColor.getcolor(hex_color, "RGB") + else: + rgb_color = ImageColor.getcolor("#" + hex_color, "RGB") else: # mode is either None or an invalid code if mode is None: @@ -120,6 +100,14 @@ class Color(commands.Cog): await ctx.send(embed=wrong_mode_embed) return + (r, g, b) = rgb_color + discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) + all_colors = self.get_color_fields(rgb_color) + hex_color = all_colors[1]["value"].replace("» hex ", "") + if color_name is None: + logger.debug(f"Find color name from hex color: {hex_color}") + color_name = self.match_color_hex(hex_color) + async with ctx.typing(): main_embed = Embed( title=color_name, @@ -239,11 +227,11 @@ class Color(commands.Cog): return all_fields @staticmethod - def match_color(input_hex_color: str) -> str: + def match_color_name(input_color_name: str) -> str: """Use fuzzy matching to return a hex color code based on the user's input.""" try: match, certainty, _ = process.extractOne( - query=input_hex_color, + query=input_color_name, choices=COLOR_MAPPING.keys(), score_cutoff=50 ) @@ -253,9 +241,89 @@ class Color(commands.Cog): return match, hex_match except TypeError: match = "No color name match found." - hex_match = input_hex_color + hex_match = input_color_name return match, hex_match + @staticmethod + def match_color_hex(input_hex_color: str) -> str: + """Use fuzzy matching to return a hex color code based on the user's input.""" + try: + match, certainty, _ = process.extractOne( + query=input_hex_color, + choices=COLOR_MAPPING.values(), + score_cutoff=80 + ) + logger.debug(f"{match = }, {certainty = }") + color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0] + logger.debug(f"{color_name = }") + return color_name + except TypeError: + color_name = "No color name match found." + return color_name + + @staticmethod + def tuple_create(input_color: str) -> tuple[int, int, int]: + """ + Create a tuple of integers based on user's input. + + Can handle inputs of the types: + (100, 100, 100) + 100, 100, 100 + 100 100 100 + """ + if "(" in input_color: + remove = "[() ]" + color_tuple = re.sub(remove, "", input_color) + color_tuple = tuple(map(int, color_tuple.split(","))) + elif "," in input_color: + color_tuple = tuple(map(int, input_color.split(","))) + else: + color_tuple = tuple(map(int, input_color.split(" "))) + return color_tuple + + @staticmethod + def hsv_to_rgb(input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsv color to rgb color.""" + (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped + h = h / 360 + s = s / 100 + v = v / 100 + rgb_color = colorsys.hsv_to_rgb(h, s, v) + (r, g, b) = rgb_color + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + rgb_color = (r, g, b) + return rgb_color + + @staticmethod + def hsl_to_rgb(input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsl color to rgb color.""" + (h, s, l) = input_color + h = h / 360 + s = s / 100 + l = l / 100 # noqa: E741 It's little `L`, Reason: To maintain consistency. + rgb_color = colorsys.hls_to_rgb(h, l, s) + (r, g, b) = rgb_color + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + rgb_color = (r, g, b) + return rgb_color + + @staticmethod + def cmyk_to_rgb(input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: + """Function to convert cmyk color to rgb color.""" + c = input_color[0] + m = input_color[1] + y = input_color[2] + k = input_color[3] + r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) + g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) + b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) + rgb_color = (r, g, b) + return rgb_color + def setup(bot: Bot) -> None: """Load the Color Cog.""" -- cgit v1.2.3 From 0b59a5e8d110e1aa7e5d76addcf6a92ee5f89ace Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Mon, 20 Sep 2021 17:20:27 +0200 Subject: Emoji: make the datetimes offset-naive You know the drill, due to discord.py 2.0a0 datetimes are now offset-aware, breaking some code. Closes python-discord/sir-lancebot#875 --- bot/exts/utilities/emoji.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/emoji.py b/bot/exts/utilities/emoji.py index 55d6b8e9..83df39cc 100644 --- a/bot/exts/utilities/emoji.py +++ b/bot/exts/utilities/emoji.py @@ -107,8 +107,8 @@ class Emojis(commands.Cog): title=f"Emoji Information: {emoji.name}", description=textwrap.dedent(f""" **Name:** {emoji.name} - **Created:** {time_since(emoji.created_at, precision="hours")} - **Date:** {datetime.strftime(emoji.created_at, "%d/%m/%Y")} + **Created:** {time_since(emoji.created_at.replace(tzinfo=None), precision="hours")} + **Date:** {datetime.strftime(emoji.created_at.replace(tzinfo=None), "%d/%m/%Y")} **ID:** {emoji.id} """), color=Color.blurple(), -- cgit v1.2.3 From 54880a121a0117639d935384905d2f1f0c6f606d Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Tue, 21 Sep 2021 06:51:55 -0400 Subject: fix: remove redundant rgb_color variable The conversion functions from hsv, hsl and cmyk now return r, g, b instead of a variable rgb_tuple. --- bot/exts/utilities/color.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 9b1f3776..542a2e19 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -293,8 +293,7 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - rgb_color = (r, g, b) - return rgb_color + return r, g, b @staticmethod def hsl_to_rgb(input_color: tuple[int, int, int]) -> tuple[int, int, int]: @@ -308,8 +307,7 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - rgb_color = (r, g, b) - return rgb_color + return r, g, b @staticmethod def cmyk_to_rgb(input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: @@ -321,8 +319,7 @@ class Color(commands.Cog): r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) - rgb_color = (r, g, b) - return rgb_color + return r, g, b def setup(bot: Bot) -> None: -- cgit v1.2.3 From 835e4469938ab2b490dcf752d2fcecf117bd0a62 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 22 Sep 2021 09:24:23 -0400 Subject: fix: create subcommands and restructure script -Makes "main" function `color_embed` that takes an rgb tuple, calls `all_colors` to get all other color types, gets a name from the hex color, creates embed, calls `create_thumbnail` to get image, and then sends main embed. -Makes functions `xxx_to_rgb` functions to call `color_embed` -Creates new `hex_to_rgb` function -TODO: test all functions and continue restructure. --- bot/exts/utilities/color.py | 81 +++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 36 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 542a2e19..5d37cbee 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -47,35 +47,16 @@ class Color(commands.Cog): logger.debug(f"{user_color = }") color_name = None if mode.lower() == "hex": - hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", user_color) - if hex_match: - hex_color = int(hex(int(user_color.replace("#", ""), 16)), 0) - if "#" in user_color: - rgb_color = ImageColor.getcolor(user_color, "RGB") - elif "0x" in user_color: - hex_ = user_color.replace("0x", "#") - rgb_color = ImageColor.getcolor(hex_, "RGB") - else: - hex_ = "#" + user_color - rgb_color = ImageColor.getcolor(hex_, "RGB") - else: - await ctx.send( - embed=Embed( - title="There was an issue converting the hex color code.", - description=ERROR_MSG.format(user_color=user_color), - ) - ) + self.hex_to_rgb(ctx, user_color) elif mode.lower() == "rgb": rgb_color = self.tuple_create(user_color) + self.embed_color(rgb_color) elif mode.lower() == "hsv": - hsv_temp = self.tuple_create(user_color) - rgb_color = self.hsv_to_rgb(hsv_temp) + self.hsv_to_rgb(user_color) elif mode.lower() == "hsl": - hsl_temp = self.tuple_create(user_color) - rgb_color = self.hsl_to_rgb(hsl_temp) + self.hsl_to_rgb(user_color) elif mode.lower() == "cmyk": - cmyk_temp = self.tuple_create(user_color) - rgb_color = self.cmyk_to_rgb(cmyk_temp) + self.cmyk_to_rgb(user_color) elif mode.lower() == "name": color_name, hex_color = self.match_color_name(user_color) if "#" in hex_color: @@ -100,6 +81,13 @@ class Color(commands.Cog): await ctx.send(embed=wrong_mode_embed) return + async def color_embed( + self, + ctx: commands.Context, + rgb_color: tuple[int, int, int], + color_name: str = None + ) -> None: + """Take a RGB color tuple, create embed, and send.""" (r, g, b) = rgb_color discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) all_colors = self.get_color_fields(rgb_color) @@ -281,9 +269,9 @@ class Color(commands.Cog): color_tuple = tuple(map(int, input_color.split(" "))) return color_tuple - @staticmethod - def hsv_to_rgb(input_color: tuple[int, int, int]) -> tuple[int, int, int]: - """Function to convert hsv color to rgb color.""" + def hsv_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsv color to rgb color and send main embed..""" + input_color = self.tuple_create(input_color) (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped h = h / 360 s = s / 100 @@ -293,11 +281,11 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - return r, g, b + self.color_embed((r, g, b)) - @staticmethod - def hsl_to_rgb(input_color: tuple[int, int, int]) -> tuple[int, int, int]: - """Function to convert hsl color to rgb color.""" + def hsl_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsl color to rgb color and send main embed..""" + input_color = self.tuple_create(input_color) (h, s, l) = input_color h = h / 360 s = s / 100 @@ -307,11 +295,11 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - return r, g, b + self.color_embed((r, g, b)) - @staticmethod - def cmyk_to_rgb(input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: - """Function to convert cmyk color to rgb color.""" + def cmyk_to_rgb(self, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: + """Function to convert cmyk color to rgb color and send main embed..""" + input_color = self.tuple_create(input_color) c = input_color[0] m = input_color[1] y = input_color[2] @@ -319,7 +307,28 @@ class Color(commands.Cog): r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) - return r, g, b + self.color_embed((r, g, b)) + + async def hex_to_rgb(self, ctx: commands.Context, hex_string: str) -> None: + """Create rgb color from hex string and send main embed.""" + hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", hex_string) + if hex_match: + if "#" in hex_string: + rgb_color = ImageColor.getcolor(hex_string, "RGB") + elif "0x" in hex_string: + hex_ = hex_string.replace("0x", "#") + rgb_color = ImageColor.getcolor(hex_, "RGB") + else: + hex_ = "#" + hex_string + rgb_color = ImageColor.getcolor(hex_, "RGB") + self.color_embed(rgb_color) + else: + await ctx.send( + embed=Embed( + title="There was an issue converting the hex color code.", + description=ERROR_MSG.format(user_color=hex_string), + ) + ) def setup(bot: Bot) -> None: -- cgit v1.2.3 From 7694caccf42ac05c373b2f8fcc180205d43d7883 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 22 Sep 2021 09:38:34 -0400 Subject: fix: restructure script --- bot/exts/utilities/color.py | 225 ++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 110 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 5d37cbee..67068809 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -50,19 +50,20 @@ class Color(commands.Cog): self.hex_to_rgb(ctx, user_color) elif mode.lower() == "rgb": rgb_color = self.tuple_create(user_color) - self.embed_color(rgb_color) + self.color_embed(ctx, rgb_color) elif mode.lower() == "hsv": - self.hsv_to_rgb(user_color) + self.hsv_to_rgb(ctx, user_color) elif mode.lower() == "hsl": - self.hsl_to_rgb(user_color) + self.hsl_to_rgb(ctx, user_color) elif mode.lower() == "cmyk": - self.cmyk_to_rgb(user_color) + self.cmyk_to_rgb(ctx, user_color) elif mode.lower() == "name": color_name, hex_color = self.match_color_name(user_color) if "#" in hex_color: rgb_color = ImageColor.getcolor(hex_color, "RGB") else: rgb_color = ImageColor.getcolor("#" + hex_color, "RGB") + self.color_embed(ctx, rgb_color, color_name) else: # mode is either None or an invalid code if mode is None: @@ -81,44 +82,94 @@ class Color(commands.Cog): await ctx.send(embed=wrong_mode_embed) return - async def color_embed( - self, - ctx: commands.Context, - rgb_color: tuple[int, int, int], - color_name: str = None - ) -> None: - """Take a RGB color tuple, create embed, and send.""" - (r, g, b) = rgb_color - discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) - all_colors = self.get_color_fields(rgb_color) - hex_color = all_colors[1]["value"].replace("» hex ", "") - if color_name is None: - logger.debug(f"Find color name from hex color: {hex_color}") - color_name = self.match_color_hex(hex_color) + @staticmethod + def tuple_create(input_color: str) -> tuple[int, int, int]: + """ + Create a tuple of integers based on user's input. - async with ctx.typing(): - main_embed = Embed( - title=color_name, - description='(Approx..)', - color=discord_rgb_int, + Can handle inputs of the types: + (100, 100, 100) + 100, 100, 100 + 100 100 100 + """ + if "(" in input_color: + remove = "[() ]" + color_tuple = re.sub(remove, "", input_color) + color_tuple = tuple(map(int, color_tuple.split(","))) + elif "," in input_color: + color_tuple = tuple(map(int, input_color.split(","))) + else: + color_tuple = tuple(map(int, input_color.split(" "))) + return color_tuple + + async def hex_to_rgb(self, ctx: commands.Context, hex_string: str) -> None: + """Function to convert hex color to rgb color and send main embed.""" + hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", hex_string) + if hex_match: + if "#" in hex_string: + rgb_color = ImageColor.getcolor(hex_string, "RGB") + elif "0x" in hex_string: + hex_ = hex_string.replace("0x", "#") + rgb_color = ImageColor.getcolor(hex_, "RGB") + else: + hex_ = "#" + hex_string + rgb_color = ImageColor.getcolor(hex_, "RGB") + self.color_embed(rgb_color) + else: + await ctx.send( + embed=Embed( + title="There was an issue converting the hex color code.", + description=ERROR_MSG.format(user_color=hex_string), + ) ) - file = await self.create_thumbnail_attachment(rgb_color) - main_embed.set_thumbnail(url="attachment://color.png") - fields = self.get_color_fields(rgb_color) + def hsv_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsv color to rgb color and send main embed.""" + input_color = self.tuple_create(input_color) + (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped + h = h / 360 + s = s / 100 + v = v / 100 + rgb_color = colorsys.hsv_to_rgb(h, s, v) + (r, g, b) = rgb_color + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + self.color_embed((r, g, b)) - for field in fields: - main_embed.add_field( - name=field['name'], - value=field['value'], - inline=False, - ) + def hsl_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsl color to rgb color and send main embed.""" + input_color = self.tuple_create(input_color) + (h, s, l) = input_color + h = h / 360 + s = s / 100 + l = l / 100 # noqa: E741 It's little `L`, Reason: To maintain consistency. + rgb_color = colorsys.hls_to_rgb(h, l, s) + (r, g, b) = rgb_color + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + self.color_embed((r, g, b)) - await ctx.send(file=file, embed=main_embed) + def cmyk_to_rgb(self, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: + """Function to convert cmyk color to rgb color and send main embed.""" + input_color = self.tuple_create(input_color) + c = input_color[0] + m = input_color[1] + y = input_color[2] + k = input_color[3] + r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) + g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) + b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) + self.color_embed((r, g, b)) @staticmethod - async def create_thumbnail_attachment(color: str) -> File: - """Generate a thumbnail from `color`.""" + async def create_thumbnail_attachment(color: tuple[int, int, int]) -> File: + """ + Generate a thumbnail from `color`. + + Assumes that color is an rgb tuple. + """ thumbnail = Image.new("RGB", (100, 100), color=color) bufferedio = BytesIO() thumbnail.save(bufferedio, format="PNG") @@ -249,86 +300,40 @@ class Color(commands.Cog): color_name = "No color name match found." return color_name - @staticmethod - def tuple_create(input_color: str) -> tuple[int, int, int]: - """ - Create a tuple of integers based on user's input. - - Can handle inputs of the types: - (100, 100, 100) - 100, 100, 100 - 100 100 100 - """ - if "(" in input_color: - remove = "[() ]" - color_tuple = re.sub(remove, "", input_color) - color_tuple = tuple(map(int, color_tuple.split(","))) - elif "," in input_color: - color_tuple = tuple(map(int, input_color.split(","))) - else: - color_tuple = tuple(map(int, input_color.split(" "))) - return color_tuple - - def hsv_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: - """Function to convert hsv color to rgb color and send main embed..""" - input_color = self.tuple_create(input_color) - (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped - h = h / 360 - s = s / 100 - v = v / 100 - rgb_color = colorsys.hsv_to_rgb(h, s, v) + async def color_embed( + self, + ctx: commands.Context, + rgb_color: tuple[int, int, int], + color_name: str = None + ) -> None: + """Take a RGB color tuple, create embed, and send.""" (r, g, b) = rgb_color - r = int(r * 255) - g = int(g * 255) - b = int(b * 255) - self.color_embed((r, g, b)) + discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) + all_colors = self.get_color_fields(rgb_color) + hex_color = all_colors[1]["value"].replace("» hex ", "") + if color_name is None: + logger.debug(f"Find color name from hex color: {hex_color}") + color_name = self.match_color_hex(hex_color) - def hsl_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: - """Function to convert hsl color to rgb color and send main embed..""" - input_color = self.tuple_create(input_color) - (h, s, l) = input_color - h = h / 360 - s = s / 100 - l = l / 100 # noqa: E741 It's little `L`, Reason: To maintain consistency. - rgb_color = colorsys.hls_to_rgb(h, l, s) - (r, g, b) = rgb_color - r = int(r * 255) - g = int(g * 255) - b = int(b * 255) - self.color_embed((r, g, b)) + async with ctx.typing(): + main_embed = Embed( + title=color_name, + description='(Approx..)', + color=discord_rgb_int, + ) - def cmyk_to_rgb(self, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: - """Function to convert cmyk color to rgb color and send main embed..""" - input_color = self.tuple_create(input_color) - c = input_color[0] - m = input_color[1] - y = input_color[2] - k = input_color[3] - r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) - g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) - b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) - self.color_embed((r, g, b)) + file = await self.create_thumbnail_attachment(rgb_color) + main_embed.set_thumbnail(url="attachment://color.png") + fields = self.get_color_fields(rgb_color) - async def hex_to_rgb(self, ctx: commands.Context, hex_string: str) -> None: - """Create rgb color from hex string and send main embed.""" - hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", hex_string) - if hex_match: - if "#" in hex_string: - rgb_color = ImageColor.getcolor(hex_string, "RGB") - elif "0x" in hex_string: - hex_ = hex_string.replace("0x", "#") - rgb_color = ImageColor.getcolor(hex_, "RGB") - else: - hex_ = "#" + hex_string - rgb_color = ImageColor.getcolor(hex_, "RGB") - self.color_embed(rgb_color) - else: - await ctx.send( - embed=Embed( - title="There was an issue converting the hex color code.", - description=ERROR_MSG.format(user_color=hex_string), + for field in fields: + main_embed.add_field( + name=field['name'], + value=field['value'], + inline=False, ) - ) + + await ctx.send(file=file, embed=main_embed) def setup(bot: Bot) -> None: -- cgit v1.2.3 From d9250cf76c6e45df0b2b5c6b2bcab90de7b70520 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 22 Sep 2021 09:39:37 -0400 Subject: fix: remove `get_color_fields` call in color_embed --- bot/exts/utilities/color.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 67068809..5cdc5083 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -324,9 +324,8 @@ class Color(commands.Cog): file = await self.create_thumbnail_attachment(rgb_color) main_embed.set_thumbnail(url="attachment://color.png") - fields = self.get_color_fields(rgb_color) - for field in fields: + for field in all_colors: main_embed.add_field( name=field['name'], value=field['value'], -- cgit v1.2.3 From 4cf2da8233ed8b10f0bc198fda52c76306ede9ac Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 22 Sep 2021 09:50:42 -0400 Subject: chore: small code fixes and cleanup --- bot/exts/utilities/color.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 5cdc5083..e0398e02 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -29,7 +29,6 @@ with open(COLOR_JSON_PATH) as f: COLOR_MAPPING = json.load(f) -# define color command class Color(commands.Cog): """User initiated commands to receive color information.""" @@ -45,7 +44,6 @@ class Color(commands.Cog): """ logger.debug(f"{mode = }") logger.debug(f"{user_color = }") - color_name = None if mode.lower() == "hex": self.hex_to_rgb(ctx, user_color) elif mode.lower() == "rgb": @@ -235,11 +233,6 @@ class Color(commands.Cog): l = round(l * 100) # noqa: E741 It's little `L`, Reason: To maintain consistency. return h, s, l - hex_color = _rgb_to_hex(rgb_color) - cmyk_color = _rgb_to_cmyk(rgb_color) - hsv_color = _rgb_to_hsv(rgb_color) - hsl_color = _rgb_to_hsl(rgb_color) - all_fields = [ { "name": "RGB", @@ -247,19 +240,19 @@ class Color(commands.Cog): }, { "name": "HEX", - "value": f"» hex {hex_color}" + "value": f"» hex {_rgb_to_hex(rgb_color)}" }, { "name": "CMYK", - "value": f"» cmyk {cmyk_color}" + "value": f"» cmyk {_rgb_to_cmyk(rgb_color)}" }, { "name": "HSV", - "value": f"» hsv {hsv_color}" + "value": f"» hsv {_rgb_to_hsv(rgb_color)}" }, { "name": "HSL", - "value": f"» hsl {hsl_color}" + "value": f"» hsl {_rgb_to_hsl(rgb_color)}" }, ] @@ -277,11 +270,11 @@ class Color(commands.Cog): logger.debug(f"{match = }, {certainty = }") hex_match = COLOR_MAPPING[match] logger.debug(f"{hex_match = }") - return match, hex_match except TypeError: match = "No color name match found." hex_match = input_color_name - return match, hex_match + + return match, hex_match @staticmethod def match_color_hex(input_hex_color: str) -> str: @@ -295,10 +288,10 @@ class Color(commands.Cog): logger.debug(f"{match = }, {certainty = }") color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0] logger.debug(f"{color_name = }") - return color_name except TypeError: color_name = "No color name match found." - return color_name + + return color_name async def color_embed( self, -- cgit v1.2.3 From 4f52cad537e19b3313b726d099ac223a3fa31c5c Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 23 Sep 2021 00:22:10 -0400 Subject: chore: create subcommands for sending embed --- bot/exts/utilities/color.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index e0398e02..6cc03c9a 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -45,23 +45,23 @@ class Color(commands.Cog): logger.debug(f"{mode = }") logger.debug(f"{user_color = }") if mode.lower() == "hex": - self.hex_to_rgb(ctx, user_color) + await self.hex_to_rgb(ctx, user_color) elif mode.lower() == "rgb": rgb_color = self.tuple_create(user_color) - self.color_embed(ctx, rgb_color) + await self.color_embed(ctx, rgb_color) elif mode.lower() == "hsv": - self.hsv_to_rgb(ctx, user_color) + await self.hsv_to_rgb(ctx, user_color) elif mode.lower() == "hsl": - self.hsl_to_rgb(ctx, user_color) + await self.hsl_to_rgb(ctx, user_color) elif mode.lower() == "cmyk": - self.cmyk_to_rgb(ctx, user_color) + await self.cmyk_to_rgb(ctx, user_color) elif mode.lower() == "name": color_name, hex_color = self.match_color_name(user_color) if "#" in hex_color: rgb_color = ImageColor.getcolor(hex_color, "RGB") else: rgb_color = ImageColor.getcolor("#" + hex_color, "RGB") - self.color_embed(ctx, rgb_color, color_name) + await self.color_embed(ctx, rgb_color, color_name) else: # mode is either None or an invalid code if mode is None: @@ -112,7 +112,7 @@ class Color(commands.Cog): else: hex_ = "#" + hex_string rgb_color = ImageColor.getcolor(hex_, "RGB") - self.color_embed(rgb_color) + await self.color_embed(ctx, rgb_color) else: await ctx.send( embed=Embed( @@ -121,7 +121,7 @@ class Color(commands.Cog): ) ) - def hsv_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + async def hsv_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int]) -> tuple[int, int, int]: """Function to convert hsv color to rgb color and send main embed.""" input_color = self.tuple_create(input_color) (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped @@ -133,9 +133,9 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - self.color_embed((r, g, b)) + await self.color_embed(ctx, (r, g, b)) - def hsl_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + async def hsl_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int]) -> tuple[int, int, int]: """Function to convert hsl color to rgb color and send main embed.""" input_color = self.tuple_create(input_color) (h, s, l) = input_color @@ -147,9 +147,9 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - self.color_embed((r, g, b)) + await self.color_embed(ctx, (r, g, b)) - def cmyk_to_rgb(self, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: + async def cmyk_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: """Function to convert cmyk color to rgb color and send main embed.""" input_color = self.tuple_create(input_color) c = input_color[0] @@ -159,7 +159,7 @@ class Color(commands.Cog): r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) - self.color_embed((r, g, b)) + await self.color_embed(ctx, (r, g, b)) @staticmethod async def create_thumbnail_attachment(color: tuple[int, int, int]) -> File: -- cgit v1.2.3 From 78cdc0f3e29ecc2571d40a2acb53b98e1b0f0e78 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Thu, 23 Sep 2021 07:48:45 -0400 Subject: chore: make cmyk_to_rgb def multiline --- bot/exts/utilities/color.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 6cc03c9a..9e2af325 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -149,7 +149,11 @@ class Color(commands.Cog): b = int(b * 255) await self.color_embed(ctx, (r, g, b)) - async def cmyk_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: + async def cmyk_to_rgb( + self, + ctx: commands.Context, + input_color: tuple[int, int, int, int] + ) -> tuple[int, int, int]: """Function to convert cmyk color to rgb color and send main embed.""" input_color = self.tuple_create(input_color) c = input_color[0] -- cgit v1.2.3 From bae934537185c50089d854a22f76596bd4403a39 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 23 Sep 2021 21:42:24 -0400 Subject: chore: set thumbnail image to 80x80 --- bot/exts/utilities/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 6cc03c9a..1652ee19 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -168,7 +168,7 @@ class Color(commands.Cog): Assumes that color is an rgb tuple. """ - thumbnail = Image.new("RGB", (100, 100), color=color) + thumbnail = Image.new("RGB", (80, 80), color=color) bufferedio = BytesIO() thumbnail.save(bufferedio, format="PNG") bufferedio.seek(0) -- cgit v1.2.3 From b0e9ffb94f05e6ef7619b7440e00363e10928932 Mon Sep 17 00:00:00 2001 From: Objectivitix <79152594+Objectivitix@users.noreply.github.com> Date: Sun, 26 Sep 2021 18:24:30 -0300 Subject: Allow everyone to use the `.bm` command everywhere (#885) * Allow everyone to use the bm command * Add everyone role in Roles constants * Use envvars and re-order Roles section to be more organized * Fix trailing whitespace We might need to squash merge, four commits for a single small fix is too much --- bot/constants.py | 3 ++- bot/exts/utilities/bookmark.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/constants.py b/bot/constants.py index 2313bfdb..6e45632f 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -280,11 +280,12 @@ if Client.month_override is not None: class Roles(NamedTuple): + owner = 267627879762755584 admin = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896)) moderator = 267629731250176001 - owner = 267627879762755584 helpers = int(environ.get("ROLE_HELPERS", 267630620367257601)) core_developers = 587606783669829632 + everyone = int(environ.get("BOT_GUILD", 267624335836053506)) class Tokens(NamedTuple): diff --git a/bot/exts/utilities/bookmark.py b/bot/exts/utilities/bookmark.py index a91ef1c0..39d65168 100644 --- a/bot/exts/utilities/bookmark.py +++ b/bot/exts/utilities/bookmark.py @@ -7,7 +7,7 @@ import discord from discord.ext import commands from bot.bot import Bot -from bot.constants import Categories, Colours, ERROR_REPLIES, Icons, WHITELISTED_CHANNELS +from bot.constants import Colours, ERROR_REPLIES, Icons, Roles from bot.utils.converters import WrappedMessageConverter from bot.utils.decorators import whitelist_override @@ -16,7 +16,6 @@ log = logging.getLogger(__name__) # Number of seconds to wait for other users to bookmark the same message TIMEOUT = 120 BOOKMARK_EMOJI = "📌" -WHITELISTED_CATEGORIES = (Categories.help_in_use,) class Bookmark(commands.Cog): @@ -87,8 +86,8 @@ class Bookmark(commands.Cog): await message.add_reaction(BOOKMARK_EMOJI) return message - @whitelist_override(channels=WHITELISTED_CHANNELS, categories=WHITELISTED_CATEGORIES) @commands.command(name="bookmark", aliases=("bm", "pin")) + @whitelist_override(roles=(Roles.everyone,)) async def bookmark( self, ctx: commands.Context, -- cgit v1.2.3 From 443cd600e4060b4b0f58382e7da07ca245cfca00 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Mon, 27 Sep 2021 09:49:42 -0400 Subject: chore: remove doubled new line in ERROR_MSG --- bot/exts/utilities/color.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 9e2af325..7c7f4bba 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -17,11 +17,11 @@ logger = logging.getLogger(__name__) ERROR_MSG = """The color code {user_color} is not a possible color combination. -\nThe range of possible values are: -\nRGB & HSV: 0-255 -\nCMYK: 0-100% -\nHSL: 0-360 degrees -\nHex: #000000-#FFFFFF +The range of possible values are: +RGB & HSV: 0-255 +CMYK: 0-100% +HSL: 0-360 degrees +Hex: #000000-#FFFFFF """ COLOR_JSON_PATH = "bot/resources/utilities/ryanzec_colours.json" -- cgit v1.2.3 From d209638ff2c4f78cdcda49972886d4a7da69165f Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Mon, 27 Sep 2021 09:56:33 -0400 Subject: chore: remove single-use constant for json path --- bot/exts/utilities/color.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 7c7f4bba..ac7b5fc6 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -24,8 +24,7 @@ HSL: 0-360 degrees Hex: #000000-#FFFFFF """ -COLOR_JSON_PATH = "bot/resources/utilities/ryanzec_colours.json" -with open(COLOR_JSON_PATH) as f: +with open("bot/resources/utilities/ryanzec_colours.json") as f: COLOR_MAPPING = json.load(f) -- cgit v1.2.3 From 57554db4fc14f5869168622e1896a80b851e7579 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Mon, 27 Sep 2021 10:46:08 -0400 Subject: chore: remove single-use constant for json path --- bot/exts/utilities/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 57510488..6aa0c3cd 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -65,7 +65,7 @@ class Color(commands.Cog): # mode is either None or an invalid code if mode is None: no_mode_embed = Embed( - title="No 'mode' was passed, please define a color code.", + title="No mode was passed, please define a color code.", description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.", color=Colours.soft_red, ) -- cgit v1.2.3 From 9f07c20f8f2e78051ab9c0d31d45755921007156 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 3 Oct 2021 14:07:25 +0100 Subject: Give the bookmark command a better error message --- bot/exts/utilities/bookmark.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/bookmark.py b/bot/exts/utilities/bookmark.py index 39d65168..a11c366b 100644 --- a/bot/exts/utilities/bookmark.py +++ b/bot/exts/utilities/bookmark.py @@ -98,7 +98,13 @@ class Bookmark(commands.Cog): """Send the author a link to `target_message` via DMs.""" if not target_message: if not ctx.message.reference: - raise commands.UserInputError("You must either provide a valid message to bookmark, or reply to one.") + raise commands.UserInputError( + "You must either provide a valid message to bookmark, or reply to one." + "\n\nThe lookup strategy for a message is as follows (in order):" + "\n1. Lookup by '{channel ID}-{message ID}' (retrieved by shift-clicking on 'Copy ID')" + "\n2. Lookup by message ID (the message **must** have been sent after the bot last started)" + "\n3. Lookup by message URL" + ) target_message = ctx.message.reference.resolved # Prevent users from bookmarking a message in a channel they don't have access to -- cgit v1.2.3 From 6a692f03eface6e87dd5cdc1b1d415d52f9df148 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 5 Sep 2021 18:41:23 -0400 Subject: Move to utilities folder as recommended by Xith --- bot/exts/utilities/color.py | 115 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 bot/exts/utilities/color.py (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py new file mode 100644 index 00000000..dd922bf9 --- /dev/null +++ b/bot/exts/utilities/color.py @@ -0,0 +1,115 @@ +# imports +import colorsys +import logging + +import PIL +from discord import Embed +from discord.ext import commands +from rapidfuzz import process + +from bot.bot import Bot +from bot.constants import Colours + +# Planning to use discord-flags, hence require changes to poetry.lock file +# from discord.ext import flags + +logger = logging.getLogger(__name__) + +# constants if needed +# Color URLs - will be replaced by JSON file? +COLOR_JSON_PATH = ".bot//exts//resources//evergreen//" +COLOR_URL_XKCD = "https://xkcd.com/color/rgb/" +COLOR_URL_NAME_THAT_COLOR = "https://github.com/ryanzec/name-that-color/blob/master/lib/ntc.js#L116-L1681" + + +COLOR_ERROR = Embed( + title="Input color is not possible", + description="The color code {user_color} is not a possible color combination." + "\nThe range of possible values are: " + "\nRGB & HSV: 0-255" + "\nCMYK: 0-100%" + "\nHSL: 0-360 degrees" + "\nHex: #000000-#FFFFFF" +) +COLOR_EMBED = Embed( + title="{color_name}", + description="RGB" + "\n{RGB}" + "\nHSV" + "\n{HSV}" + "\nCMYK" + "\n{CMYK}" + "\nHSL" + "\n{HSL}" + "\nHex" + "\n{Hex}" +) + + +# define color command +class Color(commands.cog): + """User initiated command to receive color information.""" + + def __init__(self, bot: Bot): + self.bot = bot + + # ? possible to use discord-flags to allow user to decide on color + # https://pypi.org/project/discord-flags/ + # @flags.add_flag("--rgb", type=str) + # @flags.add_flag("--hsv", type=str) + # @flags.add_flag("--cmyk", type=str) + # @flags.add_flag("--hsl", type=str) + # @flags.add_flag("--hex", type=str) + # @flags.add_flag("--name", type=str) + # @flags.command() + @commands.command(aliases=["color", "colour"]) + @commands.cooldown(1, 10, commands.cooldowns.BucketType.user) + async def color(self, ctx: commands.Context, *, user_color: str) -> None: + """Send information on input color code or color name.""" + # need to check if user_color is RGB, HSV, CMYK, HSL, Hex or color name + # should we assume the color is RGB if not defined? + # should discord tags be used? + # need to review discord.py V2.0 + + # TODO code to check if color code is possible + await ctx.send(embed=COLOR_ERROR.format(color=user_color)) + # await ctx.send(embed=COLOR_EMBED.format( + # RGB=color_dict["RGB"], + # HSV=color_dict["HSV"], + # HSL=color_dict["HSL"], + # CMYK=color_dict["CMYK"], + # HSL=color_dict["HSL"], + # Hex=color_dict["Hex"], + # color_name=color_dict["color_name"] + # ).set_image() # url for image? + # ) + + # TODO pass for now + pass + + # if user_color in color_lists: + # # TODO fuzzy match for color + # pass + + async def color_converter(self, color: str, code_type: str) -> dict: + """Generate alternative color codes for use in the embed.""" + # TODO add code to take color and code type and return other types + # color_dict = { + # "RGB": color_RGB, + # "HSV": color_HSV, + # "HSL": color_HSL, + # "CMYK": color_CMYK, + # "HSL": color_HSL, + # "Hex": color_Hex, + # "color_name": color_name, + # } + pass + + async def photo_generator(self, color: str) -> None: + """Generate photo to use in embed.""" + # TODO need to find a way to store photo in cache to add to embed, then remove + + +def setup(bot: Bot) -> None: + """Load the Color Cog.""" + bot.add_cog(Color(bot)) -- cgit v1.2.3 From f64fd14537633439e57381ddd884cb8c2aff997c Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Mon, 6 Sep 2021 20:13:17 -0400 Subject: Continue work in progress Implemented the thumbnail creation from CyberCitizen0, worked on adding some features to the program. Notable Changes: -Check if user passes in hex color -Create thumbnail based on rgb_color To-Do: -Create hex color from rgb color -Create readable rgb color from user input Co-authored-by: Mohammad Rafivulla <77384412+CyberCitizen01@users.noreply.github.com> --- bot/exts/utilities/color.py | 140 +++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 79 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index dd922bf9..1a4f7031 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,113 +1,95 @@ # imports import colorsys import logging +import re +from io import BytesIO -import PIL -from discord import Embed +from PIL import Image, ImageColor +from discord import Embed, File from discord.ext import commands from rapidfuzz import process from bot.bot import Bot from bot.constants import Colours -# Planning to use discord-flags, hence require changes to poetry.lock file -# from discord.ext import flags logger = logging.getLogger(__name__) -# constants if needed -# Color URLs - will be replaced by JSON file? -COLOR_JSON_PATH = ".bot//exts//resources//evergreen//" -COLOR_URL_XKCD = "https://xkcd.com/color/rgb/" -COLOR_URL_NAME_THAT_COLOR = "https://github.com/ryanzec/name-that-color/blob/master/lib/ntc.js#L116-L1681" - - -COLOR_ERROR = Embed( - title="Input color is not possible", - description="The color code {user_color} is not a possible color combination." - "\nThe range of possible values are: " - "\nRGB & HSV: 0-255" - "\nCMYK: 0-100%" - "\nHSL: 0-360 degrees" - "\nHex: #000000-#FFFFFF" -) -COLOR_EMBED = Embed( - title="{color_name}", - description="RGB" - "\n{RGB}" - "\nHSV" - "\n{HSV}" - "\nCMYK" - "\n{CMYK}" - "\nHSL" - "\n{HSL}" - "\nHex" - "\n{Hex}" -) + +ERROR_MSG = """The color code {user_color} is not a possible color combination. +\nThe range of possible values are: +\nRGB & HSV: 0-255 +\nCMYK: 0-100% +\nHSL: 0-360 degrees +\nHex: #000000-#FFFFFF +""" # define color command -class Color(commands.cog): +class Color(commands.Cog): """User initiated command to receive color information.""" def __init__(self, bot: Bot): self.bot = bot - # ? possible to use discord-flags to allow user to decide on color - # https://pypi.org/project/discord-flags/ - # @flags.add_flag("--rgb", type=str) - # @flags.add_flag("--hsv", type=str) - # @flags.add_flag("--cmyk", type=str) - # @flags.add_flag("--hsl", type=str) - # @flags.add_flag("--hex", type=str) - # @flags.add_flag("--name", type=str) - # @flags.command() - @commands.command(aliases=["color", "colour"]) + @commands.command(aliases=["colour"]) @commands.cooldown(1, 10, commands.cooldowns.BucketType.user) async def color(self, ctx: commands.Context, *, user_color: str) -> None: """Send information on input color code or color name.""" # need to check if user_color is RGB, HSV, CMYK, HSL, Hex or color name # should we assume the color is RGB if not defined? - # should discord tags be used? - # need to review discord.py V2.0 - - # TODO code to check if color code is possible - await ctx.send(embed=COLOR_ERROR.format(color=user_color)) - # await ctx.send(embed=COLOR_EMBED.format( - # RGB=color_dict["RGB"], - # HSV=color_dict["HSV"], - # HSL=color_dict["HSL"], - # CMYK=color_dict["CMYK"], - # HSL=color_dict["HSL"], - # Hex=color_dict["Hex"], - # color_name=color_dict["color_name"] - # ).set_image() # url for image? - # ) - - # TODO pass for now - pass + + if "#" in user_color: + logger.info(f"{user_color = }") + hex_match = re.search(r"^#(?:[0-9a-fA-F]{3}){1,2}$", user_color) + if hex_match: + hex_color = int(hex(int(user_color.replace("#", ""), 16)), 0) + logger.info(f"{hex_color = }") + rgb_color = ImageColor.getcolor(user_color, "RGB") + else: + await ctx.send(embed=Embed( + title="An error has occured.", + description=ERROR_MSG.format(user_color=user_color) + ) + ) + + elif "RGB" or "rgb" in user_color: + rgb_parse = user_color.split() + rgb = rgb_parse[1:].replace(", ", "") + logger.info(f"{rgb = }") + logger.info(f"{rgb[0] = }") + logger.info(f"{rgb[1] = }") + logger.info(f"{rgb[2] = }") + rgb_color = tuple(rgb) + hex_color = f"0x{int(rgb[0]):02x}{int(rgb[1]):02x}{int(rgb[2]):02x}" + + main_embed = Embed( + title=user_color, + color=hex_color, + ) + async with ctx.typing(): + file = await self._create_thumbnail_attachment(rgb_color) + main_embed.set_thumbnail(url="attachment://color.png") + + await ctx.send(file=file, embed=main_embed) + + async def _create_thumbnail_attachment(self, color: str) -> File: + """Generate a thumbnail from `color`.""" + + thumbnail = Image.new("RGB", (100, 100), color=color) + bufferedio = BytesIO() + thumbnail.save(bufferedio, format="PNG") + bufferedio.seek(0) + + file = File(bufferedio, filename="color.png") + + return file + # if user_color in color_lists: # # TODO fuzzy match for color # pass - async def color_converter(self, color: str, code_type: str) -> dict: - """Generate alternative color codes for use in the embed.""" - # TODO add code to take color and code type and return other types - # color_dict = { - # "RGB": color_RGB, - # "HSV": color_HSV, - # "HSL": color_HSL, - # "CMYK": color_CMYK, - # "HSL": color_HSL, - # "Hex": color_Hex, - # "color_name": color_name, - # } - pass - - async def photo_generator(self, color: str) -> None: - """Generate photo to use in embed.""" - # TODO need to find a way to store photo in cache to add to embed, then remove def setup(bot: Bot) -> None: -- cgit v1.2.3 From 1ee1fb10570b55561fc9b8bf42c01a5cc7e8171a Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Tue, 7 Sep 2021 07:39:21 -0400 Subject: Fixing flake8 errors, code style Still a work in progress but commenting out stub code and unused imports. List of To-Do's still applies. --- bot/exts/utilities/color.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 1a4f7031..b1a77b28 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,5 +1,5 @@ # imports -import colorsys +# import colorsys import logging import re from io import BytesIO @@ -7,10 +7,10 @@ from io import BytesIO from PIL import Image, ImageColor from discord import Embed, File from discord.ext import commands -from rapidfuzz import process +# from rapidfuzz import process from bot.bot import Bot -from bot.constants import Colours +# from bot.constants import Colours logger = logging.getLogger(__name__) @@ -46,12 +46,14 @@ class Color(commands.Cog): hex_color = int(hex(int(user_color.replace("#", ""), 16)), 0) logger.info(f"{hex_color = }") rgb_color = ImageColor.getcolor(user_color, "RGB") + logger.info(f"{rgb_color = }") else: - await ctx.send(embed=Embed( + await ctx.send( + embed=Embed( title="An error has occured.", - description=ERROR_MSG.format(user_color=user_color) - ) + description=ERROR_MSG.format(user_color=user_color), ) + ) elif "RGB" or "rgb" in user_color: rgb_parse = user_color.split() @@ -64,7 +66,7 @@ class Color(commands.Cog): hex_color = f"0x{int(rgb[0]):02x}{int(rgb[1]):02x}{int(rgb[2]):02x}" main_embed = Embed( - title=user_color, + title=user_color, # need to replace with fuzzymatch color name color=hex_color, ) async with ctx.typing(): @@ -75,7 +77,6 @@ class Color(commands.Cog): async def _create_thumbnail_attachment(self, color: str) -> File: """Generate a thumbnail from `color`.""" - thumbnail = Image.new("RGB", (100, 100), color=color) bufferedio = BytesIO() thumbnail.save(bufferedio, format="PNG") @@ -85,11 +86,8 @@ class Color(commands.Cog): return file - # if user_color in color_lists: - # # TODO fuzzy match for color - # pass - + # # fuzzy match for color def setup(bot: Bot) -> None: -- cgit v1.2.3 From 0862f56c56ebf2be46b2eb2ee66af52f5c12d70c Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Tue, 7 Sep 2021 07:48:07 -0400 Subject: Add embed fields for Hex and RGB --- bot/exts/utilities/color.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index b1a77b28..b1b423e7 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -73,6 +73,16 @@ class Color(commands.Cog): file = await self._create_thumbnail_attachment(rgb_color) main_embed.set_thumbnail(url="attachment://color.png") + main_embed.add_field( + name="Hex", + value=f">>Hex #{hex_color}", + inline=False, + ) + main_embed.add_field( + name="RGB", + value=f">>RGB {rgb_color}", + inline=False, + ) await ctx.send(file=file, embed=main_embed) async def _create_thumbnail_attachment(self, color: str) -> File: -- cgit v1.2.3 From a97803d05ecadf6621f943c068000f0997c4704a Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Tue, 7 Sep 2021 12:51:45 -0400 Subject: Update code to use 'mode' variable Updated the code to parse user_input depending on the color code 'mode' passed to the command. Added stub code for future color codes and embeds if mode is None or wrong code. --- bot/exts/utilities/color.py | 59 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 13 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index b1b423e7..1efacead 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -10,7 +10,7 @@ from discord.ext import commands # from rapidfuzz import process from bot.bot import Bot -# from bot.constants import Colours +from bot.constants import Colours logger = logging.getLogger(__name__) @@ -33,13 +33,12 @@ class Color(commands.Cog): self.bot = bot @commands.command(aliases=["colour"]) - @commands.cooldown(1, 10, commands.cooldowns.BucketType.user) - async def color(self, ctx: commands.Context, *, user_color: str) -> None: + async def color(self, ctx: commands.Context, mode: str, user_color: str) -> None: """Send information on input color code or color name.""" # need to check if user_color is RGB, HSV, CMYK, HSL, Hex or color name # should we assume the color is RGB if not defined? - if "#" in user_color: + if mode.lower() == "hex": logger.info(f"{user_color = }") hex_match = re.search(r"^#(?:[0-9a-fA-F]{3}){1,2}$", user_color) if hex_match: @@ -55,15 +54,32 @@ class Color(commands.Cog): ) ) - elif "RGB" or "rgb" in user_color: - rgb_parse = user_color.split() - rgb = rgb_parse[1:].replace(", ", "") - logger.info(f"{rgb = }") - logger.info(f"{rgb[0] = }") - logger.info(f"{rgb[1] = }") - logger.info(f"{rgb[2] = }") - rgb_color = tuple(rgb) - hex_color = f"0x{int(rgb[0]):02x}{int(rgb[1]):02x}{int(rgb[2]):02x}" + elif mode.lower() == "rgb": + logger.info(f"{user_color = }") + # rgb_color = user_color + + elif mode.lower() == "hsv": + pass + elif mode.lower() == "hsl": + pass + elif mode.lower() == "cmyk": + pass + else: + # mode is either None or an invalid code + if mode is None: + no_mode_embed = Embed( + title="No 'mode' was passed, please define a color code.", + color=Colours.soft_red, + ) + await ctx.send(embed=no_mode_embed) + return + wrong_mode_embed = Embed( + title=f"The color code {mode} is not a valid option", + description="Possible modes are: Hex, RGB, HSV, HSL and CMYK.", + color=Colours.soft_red, + ) + await ctx.send(embed=wrong_mode_embed) + return main_embed = Embed( title=user_color, # need to replace with fuzzymatch color name @@ -83,6 +99,23 @@ class Color(commands.Cog): value=f">>RGB {rgb_color}", inline=False, ) + """ + main_embed.add_field( + name="HSV", + value=f">>HSV {hsv_color}", + inline=False, + ) + main_embed.add_field( + name="HSL", + value=f">>HSL {hsl_color}", + inline=False, + ) + main_embed.add_field( + name="CMYK", + value=f">>CMYK {cmyk_color}", + inline=False, + ) + """ await ctx.send(file=file, embed=main_embed) async def _create_thumbnail_attachment(self, color: str) -> File: -- cgit v1.2.3 From 815f3b56933d7a43dbc93ad357d81febf576a286 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 8 Sep 2021 10:06:47 -0400 Subject: Minor fixes --- bot/exts/utilities/color.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 1efacead..d7fff503 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,4 +1,3 @@ -# imports # import colorsys import logging import re @@ -35,16 +34,14 @@ class Color(commands.Cog): @commands.command(aliases=["colour"]) async def color(self, ctx: commands.Context, mode: str, user_color: str) -> None: """Send information on input color code or color name.""" - # need to check if user_color is RGB, HSV, CMYK, HSL, Hex or color name - # should we assume the color is RGB if not defined? - + logger.info(f"{mode = }") + logger.info(f"{user_color = }") if mode.lower() == "hex": - logger.info(f"{user_color = }") hex_match = re.search(r"^#(?:[0-9a-fA-F]{3}){1,2}$", user_color) if hex_match: hex_color = int(hex(int(user_color.replace("#", ""), 16)), 0) - logger.info(f"{hex_color = }") rgb_color = ImageColor.getcolor(user_color, "RGB") + logger.info(f"{hex_color = }") logger.info(f"{rgb_color = }") else: await ctx.send( @@ -53,11 +50,8 @@ class Color(commands.Cog): description=ERROR_MSG.format(user_color=user_color), ) ) - elif mode.lower() == "rgb": - logger.info(f"{user_color = }") - # rgb_color = user_color - + pass elif mode.lower() == "hsv": pass elif mode.lower() == "hsl": @@ -66,6 +60,7 @@ class Color(commands.Cog): pass else: # mode is either None or an invalid code + # need to handle whether user passes color name if mode is None: no_mode_embed = Embed( title="No 'mode' was passed, please define a color code.", -- cgit v1.2.3 From c87f4451206cfc1315fd32c33836f81a5e6ea300 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Fri, 10 Sep 2021 07:47:12 -0400 Subject: Test to capture all user_input --- bot/exts/utilities/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index d7fff503..c4df3e10 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -32,7 +32,7 @@ class Color(commands.Cog): self.bot = bot @commands.command(aliases=["colour"]) - async def color(self, ctx: commands.Context, mode: str, user_color: str) -> None: + async def color(self, ctx: commands.Context, mode: str, *, user_color: str) -> None: """Send information on input color code or color name.""" logger.info(f"{mode = }") logger.info(f"{user_color = }") -- cgit v1.2.3 From fc5d7ea343336a3fc62617d4042a13a208763c6c Mon Sep 17 00:00:00 2001 From: CyberCitizen01 Date: Sat, 11 Sep 2021 04:17:18 +0530 Subject: Added "colour information" and "colour conversion" features Details: https://github.com/python-discord/sir-lancebot/issues/677 NOTE: get_color_fields (line 122) method explicity requires a valid tuple of RGB values. --- bot/exts/utilities/color.py | 134 +++++++++++++++++++++++++++++++++----------- 1 file changed, 100 insertions(+), 34 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index c4df3e10..6abfc006 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,4 +1,4 @@ -# import colorsys +import colorsys import logging import re from io import BytesIO @@ -76,44 +76,28 @@ class Color(commands.Cog): await ctx.send(embed=wrong_mode_embed) return - main_embed = Embed( - title=user_color, # need to replace with fuzzymatch color name - color=hex_color, - ) async with ctx.typing(): - file = await self._create_thumbnail_attachment(rgb_color) + main_embed = Embed( + title=user_color, # need to replace with fuzzymatch color name + description='(Approx..)', + color=hex_color, + ) + + file = await self.create_thumbnail_attachment(rgb_color) main_embed.set_thumbnail(url="attachment://color.png") - main_embed.add_field( - name="Hex", - value=f">>Hex #{hex_color}", - inline=False, - ) - main_embed.add_field( - name="RGB", - value=f">>RGB {rgb_color}", - inline=False, - ) - """ - main_embed.add_field( - name="HSV", - value=f">>HSV {hsv_color}", - inline=False, - ) - main_embed.add_field( - name="HSL", - value=f">>HSL {hsl_color}", - inline=False, - ) - main_embed.add_field( - name="CMYK", - value=f">>CMYK {cmyk_color}", - inline=False, - ) - """ + fields = self.get_color_fields(rgb_color) + for field in fields: + main_embed.add_field( + name=field['name'], + value=field['value'], + inline=False, + ) + await ctx.send(file=file, embed=main_embed) - async def _create_thumbnail_attachment(self, color: str) -> File: + @staticmethod + async def create_thumbnail_attachment(color: str) -> File: """Generate a thumbnail from `color`.""" thumbnail = Image.new("RGB", (100, 100), color=color) bufferedio = BytesIO() @@ -124,6 +108,88 @@ class Color(commands.Cog): return file + @staticmethod + def get_color_fields(rgb_color: tuple[int, int, int]) -> list[dict]: + """Converts from `RGB` to `CMYK`, `HSV`, `HSL` and returns a list of fields.""" + + def _rgb_to_hex(rgb_color: tuple[int, int, int]) -> str: + """To convert from `RGB` to `Hex` notation.""" + return '#' + ''.join(hex(color)[2:].zfill(2) for color in rgb_color).upper() + + def _rgb_to_cmyk(rgb_color: tuple[int, int, int]) -> tuple[int, int, int, int]: + """To convert from `RGB` to `CMYK` color space.""" + r, g, b = rgb_color + + # RGB_SCALE -> 255 + # CMYK_SCALE -> 100 + + if (r == g == b == 0): + return 0, 0, 0, 100 # Representing Black + + # rgb [0,RGB_SCALE] -> cmy [0,1] + c = 1 - r / 255 + m = 1 - g / 255 + y = 1 - b / 255 + + # extract out k [0, 1] + min_cmy = min(c, m, y) + c = (c - min_cmy) / (1 - min_cmy) + m = (m - min_cmy) / (1 - min_cmy) + y = (y - min_cmy) / (1 - min_cmy) + k = min_cmy + + # rescale to the range [0,CMYK_SCALE] and round off + c = round(c * 100) + m = round(m * 100) + y = round(y * 100) + k = round(k * 100) + + return c, m, y, k + + def _rgb_to_hsv(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: + """To convert from `RGB` to `HSV` color space.""" + r, g, b = rgb_color + h, v, s = colorsys.rgb_to_hsv(r/float(255), g/float(255), b/float(255)) + h = round(h*360) + s = round(s*100) + v = round(v*100) + return h, s, v + + def _rgb_to_hsl(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: + """To convert from `RGB` to `HSL` color space.""" + r, g, b = rgb_color + h, l, s = colorsys.rgb_to_hls(r/float(255), g/float(255), b/float(255)) + h = round(h*360) + s = round(s*100) + l = round(l*100) # noqa: E741 It's little `L`, Reason: To maintain consistency. + return h, s, l + + hex_color = _rgb_to_hex(rgb_color) + cmyk_color = _rgb_to_cmyk(rgb_color) + hsv_color = _rgb_to_hsv(rgb_color) + hsl_color = _rgb_to_hsl(rgb_color) + + all_fields = [ + { + "name": "RGB", + "value": f"» rgb {rgb_color}\n» hex {hex_color}" + }, + { + "name": "CMYK", + "value": f"» cmyk {cmyk_color}" + }, + { + "name": "HSV", + "value": f"» hsv {hsv_color}" + }, + { + "name": "HSL", + "value": f"» hsl {hsl_color}" + }, + ] + + return all_fields + # if user_color in color_lists: # # fuzzy match for color -- cgit v1.2.3 From 4e316e7991862db76b78e16019c02461c3c52fc2 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sat, 11 Sep 2021 09:57:13 -0400 Subject: Add fuzzy match function I made a few changes, the biggest being the fuzzy match function to return a hex color code based on an input color name. Open items that I can think of so far: -Since the json file has color names and hex values, in order to use fuzzy matching for a color name the color must first be converted to hex. Currently there is only a rgb to anything function which returns values in a dictionary. -The main embed creation references the rgb_color before it is defined, should the command function be moved to the bottom of the file or just the main embed creation and sending? -When using the rgb mode, should the user be forced to do (r, g, b) or should the command handle an input of "r, g, b"? If you are reading this, thank you. --- bot/exts/utilities/color.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 6abfc006..9e199dd4 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,4 +1,5 @@ import colorsys +import json import logging import re from io import BytesIO @@ -6,7 +7,7 @@ from io import BytesIO from PIL import Image, ImageColor from discord import Embed, File from discord.ext import commands -# from rapidfuzz import process +from rapidfuzz import process from bot.bot import Bot from bot.constants import Colours @@ -23,6 +24,8 @@ ERROR_MSG = """The color code {user_color} is not a possible color combination. \nHex: #000000-#FFFFFF """ +COLOR_LIST = "bot/resources/utilities/ryanzec_colours.json" + # define color command class Color(commands.Cog): @@ -46,7 +49,7 @@ class Color(commands.Cog): else: await ctx.send( embed=Embed( - title="An error has occured.", + title="There was an issue converting the hex color code.", description=ERROR_MSG.format(user_color=user_color), ) ) @@ -58,9 +61,10 @@ class Color(commands.Cog): pass elif mode.lower() == "cmyk": pass + elif mode.lower() == "name": + color_name, hex_color = self.match_color(user_color) else: # mode is either None or an invalid code - # need to handle whether user passes color name if mode is None: no_mode_embed = Embed( title="No 'mode' was passed, please define a color code.", @@ -70,7 +74,7 @@ class Color(commands.Cog): return wrong_mode_embed = Embed( title=f"The color code {mode} is not a valid option", - description="Possible modes are: Hex, RGB, HSV, HSL and CMYK.", + description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.", color=Colours.soft_red, ) await ctx.send(embed=wrong_mode_embed) @@ -78,15 +82,15 @@ class Color(commands.Cog): async with ctx.typing(): main_embed = Embed( - title=user_color, # need to replace with fuzzymatch color name + title=color_name, description='(Approx..)', - color=hex_color, + color=rgb_color, ) file = await self.create_thumbnail_attachment(rgb_color) main_embed.set_thumbnail(url="attachment://color.png") - fields = self.get_color_fields(rgb_color) + for field in fields: main_embed.add_field( name=field['name'], @@ -94,7 +98,7 @@ class Color(commands.Cog): inline=False, ) - await ctx.send(file=file, embed=main_embed) + await ctx.send(file=file, embed=main_embed) @staticmethod async def create_thumbnail_attachment(color: str) -> File: @@ -192,6 +196,17 @@ class Color(commands.Cog): # if user_color in color_lists: # # fuzzy match for color + @staticmethod + def match_color(user_color: str) -> str: + """Use fuzzy matching to return a hex color code based on the user's input.""" + with open(COLOR_LIST) as f: + color_list = json.load(f) + logger.debug(f"{type(color_list) = }") + match, certainty, _ = process.extractOne(query=user_color, choices=color_list.keys(), score_cutoff=50) + logger.debug(f"{match = }, {certainty = }") + hex_match = color_list[match] + logger.debug(f"{hex_match = }") + return match, hex_match def setup(bot: Bot) -> None: -- cgit v1.2.3 From c6333ab48ddbc37af40cd958cd37494be7ff50e7 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sat, 11 Sep 2021 10:09:27 -0400 Subject: Remove placeholder comment --- bot/exts/utilities/color.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 9e199dd4..6452d292 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -194,8 +194,6 @@ class Color(commands.Cog): return all_fields - # if user_color in color_lists: - # # fuzzy match for color @staticmethod def match_color(user_color: str) -> str: """Use fuzzy matching to return a hex color code based on the user's input.""" -- cgit v1.2.3 From 2cc1ad0538d4edb69eddfe771bd3068d18ed54f3 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 12 Sep 2021 08:39:47 -0400 Subject: Load json file once --- bot/exts/utilities/color.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 6452d292..dd470197 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -25,6 +25,8 @@ ERROR_MSG = """The color code {user_color} is not a possible color combination. """ COLOR_LIST = "bot/resources/utilities/ryanzec_colours.json" +with open(COLOR_LIST) as f: + COLOR_JSON = json.load(f) # define color command @@ -197,12 +199,9 @@ class Color(commands.Cog): @staticmethod def match_color(user_color: str) -> str: """Use fuzzy matching to return a hex color code based on the user's input.""" - with open(COLOR_LIST) as f: - color_list = json.load(f) - logger.debug(f"{type(color_list) = }") - match, certainty, _ = process.extractOne(query=user_color, choices=color_list.keys(), score_cutoff=50) + match, certainty, _ = process.extractOne(query=user_color, choices=COLOR_JSON.keys(), score_cutoff=50) logger.debug(f"{match = }, {certainty = }") - hex_match = color_list[match] + hex_match = COLOR_JSON[match] logger.debug(f"{hex_match = }") return match, hex_match -- cgit v1.2.3 From 85de760ad006575bfb61472be0aa346406ac7d2c Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 15 Sep 2021 16:16:52 -0400 Subject: Fix Flake8 spacing errors --- bot/exts/utilities/color.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index dd470197..eb9d5f4d 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -155,19 +155,19 @@ class Color(commands.Cog): def _rgb_to_hsv(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: """To convert from `RGB` to `HSV` color space.""" r, g, b = rgb_color - h, v, s = colorsys.rgb_to_hsv(r/float(255), g/float(255), b/float(255)) - h = round(h*360) - s = round(s*100) - v = round(v*100) + h, v, s = colorsys.rgb_to_hsv(r / float(255), g / float(255), b / float(255)) + h = round(h * 360) + s = round(s * 100) + v = round(v * 100) return h, s, v def _rgb_to_hsl(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: """To convert from `RGB` to `HSL` color space.""" r, g, b = rgb_color - h, l, s = colorsys.rgb_to_hls(r/float(255), g/float(255), b/float(255)) - h = round(h*360) - s = round(s*100) - l = round(l*100) # noqa: E741 It's little `L`, Reason: To maintain consistency. + h, l, s = colorsys.rgb_to_hls(r / float(255), g / float(255), b / float(255)) + h = round(h * 360) + s = round(s * 100) + l = round(l * 100) # noqa: E741 It's little `L`, Reason: To maintain consistency. return h, s, l hex_color = _rgb_to_hex(rgb_color) -- cgit v1.2.3 From 0f777557d67bdf117eafcba3ab98192dd420cf96 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 19 Sep 2021 08:20:18 -0400 Subject: Reword json file variables and mapping --- bot/exts/utilities/color.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index eb9d5f4d..94c9d337 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -24,9 +24,9 @@ ERROR_MSG = """The color code {user_color} is not a possible color combination. \nHex: #000000-#FFFFFF """ -COLOR_LIST = "bot/resources/utilities/ryanzec_colours.json" -with open(COLOR_LIST) as f: - COLOR_JSON = json.load(f) +COLOR_JSON_PATH = "bot/resources/utilities/ryanzec_colours.json" +with open(COLOR_JSON_PATH) as f: + COLOR_MAPPING = json.load(f) # define color command @@ -199,9 +199,9 @@ class Color(commands.Cog): @staticmethod def match_color(user_color: str) -> str: """Use fuzzy matching to return a hex color code based on the user's input.""" - match, certainty, _ = process.extractOne(query=user_color, choices=COLOR_JSON.keys(), score_cutoff=50) + match, certainty, _ = process.extractOne(query=user_color, choices=COLOR_MAPPING.keys(), score_cutoff=50) logger.debug(f"{match = }, {certainty = }") - hex_match = COLOR_JSON[match] + hex_match = COLOR_MAPPING[match] logger.debug(f"{hex_match = }") return match, hex_match -- cgit v1.2.3 From 4a2b89dbde41057ba14c08f8994c6d02b3c114b8 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 19 Sep 2021 10:20:58 -0400 Subject: Continue work on hex and rgb color commands --- bot/exts/utilities/color.py | 87 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 18 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 94c9d337..2bec6ba3 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -31,23 +31,45 @@ with open(COLOR_JSON_PATH) as f: # define color command class Color(commands.Cog): - """User initiated command to receive color information.""" + """User initiated commands to receive color information.""" def __init__(self, bot: Bot): self.bot = bot @commands.command(aliases=["colour"]) async def color(self, ctx: commands.Context, mode: str, *, user_color: str) -> None: - """Send information on input color code or color name.""" - logger.info(f"{mode = }") - logger.info(f"{user_color = }") + """ + Send information on input color code or color name. + + Possible modes are: "hex", "rgb", "hsv", "hsl", "cmyk" or "name". + """ + logger.debug(f"{mode = }") + logger.debug(f"{user_color = }") if mode.lower() == "hex": - hex_match = re.search(r"^#(?:[0-9a-fA-F]{3}){1,2}$", user_color) + hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", user_color) if hex_match: hex_color = int(hex(int(user_color.replace("#", ""), 16)), 0) - rgb_color = ImageColor.getcolor(user_color, "RGB") - logger.info(f"{hex_color = }") - logger.info(f"{rgb_color = }") + if "#" in user_color: + rgb_color = ImageColor.getcolor(user_color, "RGB") + elif "0x" in user_color: + hex_ = user_color.replace("0x", "#") + rgb_color = ImageColor.getcolor(hex_, "RGB") + else: + hex_ = "#" + user_color + rgb_color = ImageColor.getcolor(hex_, "RGB") + (r, g, b) = rgb_color + discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) + all_colors = self.get_color_fields(rgb_color) + hex_color = all_colors[1]["value"].replace("» hex ", "") + cmyk_color = all_colors[2]["value"].replace("» cmyk ", "") + hsv_color = all_colors[3]["value"].replace("» hsv ", "") + hsl_color = all_colors[4]["value"].replace("» hsl ", "") + logger.debug(f"{rgb_color = }") + logger.debug(f"{hex_color = }") + logger.debug(f"{hsv_color = }") + logger.debug(f"{hsl_color = }") + logger.debug(f"{cmyk_color = }") + color_name, _ = self.match_color(hex_color) else: await ctx.send( embed=Embed( @@ -56,7 +78,22 @@ class Color(commands.Cog): ) ) elif mode.lower() == "rgb": - pass + if "(" in user_color: + remove = "[() ]" + rgb_color = re.sub(remove, "", user_color) + rgb_color = tuple(map(int, rgb_color.split(","))) + elif "," in user_color: + rgb_color = tuple(map(int, user_color.split(","))) + else: + rgb_color = tuple(map(int, user_color.split(" "))) + (r, g, b) = rgb_color + discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) + all_colors = self.get_color_fields(rgb_color) + hex_color = all_colors[1]["value"].replace("» hex ", "") + cmyk_color = all_colors[2]["value"].replace("» cmyk ", "") + hsv_color = all_colors[3]["value"].replace("» hsv ", "") + hsl_color = all_colors[4]["value"].replace("» hsl ", "") + color_name, _ = self.match_color(hex_color) elif mode.lower() == "hsv": pass elif mode.lower() == "hsl": @@ -70,6 +107,7 @@ class Color(commands.Cog): if mode is None: no_mode_embed = Embed( title="No 'mode' was passed, please define a color code.", + description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.", color=Colours.soft_red, ) await ctx.send(embed=no_mode_embed) @@ -86,7 +124,7 @@ class Color(commands.Cog): main_embed = Embed( title=color_name, description='(Approx..)', - color=rgb_color, + color=discord_rgb_int, ) file = await self.create_thumbnail_attachment(rgb_color) @@ -120,7 +158,7 @@ class Color(commands.Cog): def _rgb_to_hex(rgb_color: tuple[int, int, int]) -> str: """To convert from `RGB` to `Hex` notation.""" - return '#' + ''.join(hex(color)[2:].zfill(2) for color in rgb_color).upper() + return '#' + ''.join(hex(int(color))[2:].zfill(2) for color in rgb_color).upper() def _rgb_to_cmyk(rgb_color: tuple[int, int, int]) -> tuple[int, int, int, int]: """To convert from `RGB` to `CMYK` color space.""" @@ -178,7 +216,11 @@ class Color(commands.Cog): all_fields = [ { "name": "RGB", - "value": f"» rgb {rgb_color}\n» hex {hex_color}" + "value": f"» rgb {rgb_color}" + }, + { + "name": "HEX", + "value": f"» hex {hex_color}" }, { "name": "CMYK", @@ -197,13 +239,22 @@ class Color(commands.Cog): return all_fields @staticmethod - def match_color(user_color: str) -> str: + def match_color(input_hex_color: str) -> str: """Use fuzzy matching to return a hex color code based on the user's input.""" - match, certainty, _ = process.extractOne(query=user_color, choices=COLOR_MAPPING.keys(), score_cutoff=50) - logger.debug(f"{match = }, {certainty = }") - hex_match = COLOR_MAPPING[match] - logger.debug(f"{hex_match = }") - return match, hex_match + try: + match, certainty, _ = process.extractOne( + query=input_hex_color, + choices=COLOR_MAPPING.keys(), + score_cutoff=50 + ) + logger.debug(f"{match = }, {certainty = }") + hex_match = COLOR_MAPPING[match] + logger.debug(f"{hex_match = }") + return match, hex_match + except TypeError: + match = "No color name match found." + hex_match = input_hex_color + return match, hex_match def setup(bot: Bot) -> None: -- cgit v1.2.3 From 620142ba3586fce88a61886288bafd5f078f95e7 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 19 Sep 2021 17:49:32 -0400 Subject: Add all color modes and name matching --- bot/exts/utilities/color.py | 140 ++++++++++++++++++++++++++++++++------------ 1 file changed, 104 insertions(+), 36 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 2bec6ba3..9b1f3776 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -45,6 +45,7 @@ class Color(commands.Cog): """ logger.debug(f"{mode = }") logger.debug(f"{user_color = }") + color_name = None if mode.lower() == "hex": hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", user_color) if hex_match: @@ -57,19 +58,6 @@ class Color(commands.Cog): else: hex_ = "#" + user_color rgb_color = ImageColor.getcolor(hex_, "RGB") - (r, g, b) = rgb_color - discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) - all_colors = self.get_color_fields(rgb_color) - hex_color = all_colors[1]["value"].replace("» hex ", "") - cmyk_color = all_colors[2]["value"].replace("» cmyk ", "") - hsv_color = all_colors[3]["value"].replace("» hsv ", "") - hsl_color = all_colors[4]["value"].replace("» hsl ", "") - logger.debug(f"{rgb_color = }") - logger.debug(f"{hex_color = }") - logger.debug(f"{hsv_color = }") - logger.debug(f"{hsl_color = }") - logger.debug(f"{cmyk_color = }") - color_name, _ = self.match_color(hex_color) else: await ctx.send( embed=Embed( @@ -78,30 +66,22 @@ class Color(commands.Cog): ) ) elif mode.lower() == "rgb": - if "(" in user_color: - remove = "[() ]" - rgb_color = re.sub(remove, "", user_color) - rgb_color = tuple(map(int, rgb_color.split(","))) - elif "," in user_color: - rgb_color = tuple(map(int, user_color.split(","))) - else: - rgb_color = tuple(map(int, user_color.split(" "))) - (r, g, b) = rgb_color - discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) - all_colors = self.get_color_fields(rgb_color) - hex_color = all_colors[1]["value"].replace("» hex ", "") - cmyk_color = all_colors[2]["value"].replace("» cmyk ", "") - hsv_color = all_colors[3]["value"].replace("» hsv ", "") - hsl_color = all_colors[4]["value"].replace("» hsl ", "") - color_name, _ = self.match_color(hex_color) + rgb_color = self.tuple_create(user_color) elif mode.lower() == "hsv": - pass + hsv_temp = self.tuple_create(user_color) + rgb_color = self.hsv_to_rgb(hsv_temp) elif mode.lower() == "hsl": - pass + hsl_temp = self.tuple_create(user_color) + rgb_color = self.hsl_to_rgb(hsl_temp) elif mode.lower() == "cmyk": - pass + cmyk_temp = self.tuple_create(user_color) + rgb_color = self.cmyk_to_rgb(cmyk_temp) elif mode.lower() == "name": - color_name, hex_color = self.match_color(user_color) + color_name, hex_color = self.match_color_name(user_color) + if "#" in hex_color: + rgb_color = ImageColor.getcolor(hex_color, "RGB") + else: + rgb_color = ImageColor.getcolor("#" + hex_color, "RGB") else: # mode is either None or an invalid code if mode is None: @@ -120,6 +100,14 @@ class Color(commands.Cog): await ctx.send(embed=wrong_mode_embed) return + (r, g, b) = rgb_color + discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) + all_colors = self.get_color_fields(rgb_color) + hex_color = all_colors[1]["value"].replace("» hex ", "") + if color_name is None: + logger.debug(f"Find color name from hex color: {hex_color}") + color_name = self.match_color_hex(hex_color) + async with ctx.typing(): main_embed = Embed( title=color_name, @@ -239,11 +227,11 @@ class Color(commands.Cog): return all_fields @staticmethod - def match_color(input_hex_color: str) -> str: + def match_color_name(input_color_name: str) -> str: """Use fuzzy matching to return a hex color code based on the user's input.""" try: match, certainty, _ = process.extractOne( - query=input_hex_color, + query=input_color_name, choices=COLOR_MAPPING.keys(), score_cutoff=50 ) @@ -253,9 +241,89 @@ class Color(commands.Cog): return match, hex_match except TypeError: match = "No color name match found." - hex_match = input_hex_color + hex_match = input_color_name return match, hex_match + @staticmethod + def match_color_hex(input_hex_color: str) -> str: + """Use fuzzy matching to return a hex color code based on the user's input.""" + try: + match, certainty, _ = process.extractOne( + query=input_hex_color, + choices=COLOR_MAPPING.values(), + score_cutoff=80 + ) + logger.debug(f"{match = }, {certainty = }") + color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0] + logger.debug(f"{color_name = }") + return color_name + except TypeError: + color_name = "No color name match found." + return color_name + + @staticmethod + def tuple_create(input_color: str) -> tuple[int, int, int]: + """ + Create a tuple of integers based on user's input. + + Can handle inputs of the types: + (100, 100, 100) + 100, 100, 100 + 100 100 100 + """ + if "(" in input_color: + remove = "[() ]" + color_tuple = re.sub(remove, "", input_color) + color_tuple = tuple(map(int, color_tuple.split(","))) + elif "," in input_color: + color_tuple = tuple(map(int, input_color.split(","))) + else: + color_tuple = tuple(map(int, input_color.split(" "))) + return color_tuple + + @staticmethod + def hsv_to_rgb(input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsv color to rgb color.""" + (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped + h = h / 360 + s = s / 100 + v = v / 100 + rgb_color = colorsys.hsv_to_rgb(h, s, v) + (r, g, b) = rgb_color + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + rgb_color = (r, g, b) + return rgb_color + + @staticmethod + def hsl_to_rgb(input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsl color to rgb color.""" + (h, s, l) = input_color + h = h / 360 + s = s / 100 + l = l / 100 # noqa: E741 It's little `L`, Reason: To maintain consistency. + rgb_color = colorsys.hls_to_rgb(h, l, s) + (r, g, b) = rgb_color + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + rgb_color = (r, g, b) + return rgb_color + + @staticmethod + def cmyk_to_rgb(input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: + """Function to convert cmyk color to rgb color.""" + c = input_color[0] + m = input_color[1] + y = input_color[2] + k = input_color[3] + r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) + g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) + b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) + rgb_color = (r, g, b) + return rgb_color + def setup(bot: Bot) -> None: """Load the Color Cog.""" -- cgit v1.2.3 From fe2b7d442bec05ab02836e2c5d7613d738fd5151 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Tue, 21 Sep 2021 06:51:55 -0400 Subject: fix: remove redundant rgb_color variable The conversion functions from hsv, hsl and cmyk now return r, g, b instead of a variable rgb_tuple. --- bot/exts/utilities/color.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 9b1f3776..542a2e19 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -293,8 +293,7 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - rgb_color = (r, g, b) - return rgb_color + return r, g, b @staticmethod def hsl_to_rgb(input_color: tuple[int, int, int]) -> tuple[int, int, int]: @@ -308,8 +307,7 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - rgb_color = (r, g, b) - return rgb_color + return r, g, b @staticmethod def cmyk_to_rgb(input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: @@ -321,8 +319,7 @@ class Color(commands.Cog): r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) - rgb_color = (r, g, b) - return rgb_color + return r, g, b def setup(bot: Bot) -> None: -- cgit v1.2.3 From c32a3f622be9c5d95602fb455bb557c3cd8a10b8 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 22 Sep 2021 09:24:23 -0400 Subject: fix: create subcommands and restructure script -Makes "main" function `color_embed` that takes an rgb tuple, calls `all_colors` to get all other color types, gets a name from the hex color, creates embed, calls `create_thumbnail` to get image, and then sends main embed. -Makes functions `xxx_to_rgb` functions to call `color_embed` -Creates new `hex_to_rgb` function -TODO: test all functions and continue restructure. --- bot/exts/utilities/color.py | 81 +++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 36 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 542a2e19..5d37cbee 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -47,35 +47,16 @@ class Color(commands.Cog): logger.debug(f"{user_color = }") color_name = None if mode.lower() == "hex": - hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", user_color) - if hex_match: - hex_color = int(hex(int(user_color.replace("#", ""), 16)), 0) - if "#" in user_color: - rgb_color = ImageColor.getcolor(user_color, "RGB") - elif "0x" in user_color: - hex_ = user_color.replace("0x", "#") - rgb_color = ImageColor.getcolor(hex_, "RGB") - else: - hex_ = "#" + user_color - rgb_color = ImageColor.getcolor(hex_, "RGB") - else: - await ctx.send( - embed=Embed( - title="There was an issue converting the hex color code.", - description=ERROR_MSG.format(user_color=user_color), - ) - ) + self.hex_to_rgb(ctx, user_color) elif mode.lower() == "rgb": rgb_color = self.tuple_create(user_color) + self.embed_color(rgb_color) elif mode.lower() == "hsv": - hsv_temp = self.tuple_create(user_color) - rgb_color = self.hsv_to_rgb(hsv_temp) + self.hsv_to_rgb(user_color) elif mode.lower() == "hsl": - hsl_temp = self.tuple_create(user_color) - rgb_color = self.hsl_to_rgb(hsl_temp) + self.hsl_to_rgb(user_color) elif mode.lower() == "cmyk": - cmyk_temp = self.tuple_create(user_color) - rgb_color = self.cmyk_to_rgb(cmyk_temp) + self.cmyk_to_rgb(user_color) elif mode.lower() == "name": color_name, hex_color = self.match_color_name(user_color) if "#" in hex_color: @@ -100,6 +81,13 @@ class Color(commands.Cog): await ctx.send(embed=wrong_mode_embed) return + async def color_embed( + self, + ctx: commands.Context, + rgb_color: tuple[int, int, int], + color_name: str = None + ) -> None: + """Take a RGB color tuple, create embed, and send.""" (r, g, b) = rgb_color discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) all_colors = self.get_color_fields(rgb_color) @@ -281,9 +269,9 @@ class Color(commands.Cog): color_tuple = tuple(map(int, input_color.split(" "))) return color_tuple - @staticmethod - def hsv_to_rgb(input_color: tuple[int, int, int]) -> tuple[int, int, int]: - """Function to convert hsv color to rgb color.""" + def hsv_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsv color to rgb color and send main embed..""" + input_color = self.tuple_create(input_color) (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped h = h / 360 s = s / 100 @@ -293,11 +281,11 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - return r, g, b + self.color_embed((r, g, b)) - @staticmethod - def hsl_to_rgb(input_color: tuple[int, int, int]) -> tuple[int, int, int]: - """Function to convert hsl color to rgb color.""" + def hsl_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsl color to rgb color and send main embed..""" + input_color = self.tuple_create(input_color) (h, s, l) = input_color h = h / 360 s = s / 100 @@ -307,11 +295,11 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - return r, g, b + self.color_embed((r, g, b)) - @staticmethod - def cmyk_to_rgb(input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: - """Function to convert cmyk color to rgb color.""" + def cmyk_to_rgb(self, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: + """Function to convert cmyk color to rgb color and send main embed..""" + input_color = self.tuple_create(input_color) c = input_color[0] m = input_color[1] y = input_color[2] @@ -319,7 +307,28 @@ class Color(commands.Cog): r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) - return r, g, b + self.color_embed((r, g, b)) + + async def hex_to_rgb(self, ctx: commands.Context, hex_string: str) -> None: + """Create rgb color from hex string and send main embed.""" + hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", hex_string) + if hex_match: + if "#" in hex_string: + rgb_color = ImageColor.getcolor(hex_string, "RGB") + elif "0x" in hex_string: + hex_ = hex_string.replace("0x", "#") + rgb_color = ImageColor.getcolor(hex_, "RGB") + else: + hex_ = "#" + hex_string + rgb_color = ImageColor.getcolor(hex_, "RGB") + self.color_embed(rgb_color) + else: + await ctx.send( + embed=Embed( + title="There was an issue converting the hex color code.", + description=ERROR_MSG.format(user_color=hex_string), + ) + ) def setup(bot: Bot) -> None: -- cgit v1.2.3 From eeebd656d233541246a407d839a0436f893a4b43 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 22 Sep 2021 09:38:34 -0400 Subject: fix: restructure script --- bot/exts/utilities/color.py | 225 ++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 110 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 5d37cbee..67068809 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -50,19 +50,20 @@ class Color(commands.Cog): self.hex_to_rgb(ctx, user_color) elif mode.lower() == "rgb": rgb_color = self.tuple_create(user_color) - self.embed_color(rgb_color) + self.color_embed(ctx, rgb_color) elif mode.lower() == "hsv": - self.hsv_to_rgb(user_color) + self.hsv_to_rgb(ctx, user_color) elif mode.lower() == "hsl": - self.hsl_to_rgb(user_color) + self.hsl_to_rgb(ctx, user_color) elif mode.lower() == "cmyk": - self.cmyk_to_rgb(user_color) + self.cmyk_to_rgb(ctx, user_color) elif mode.lower() == "name": color_name, hex_color = self.match_color_name(user_color) if "#" in hex_color: rgb_color = ImageColor.getcolor(hex_color, "RGB") else: rgb_color = ImageColor.getcolor("#" + hex_color, "RGB") + self.color_embed(ctx, rgb_color, color_name) else: # mode is either None or an invalid code if mode is None: @@ -81,44 +82,94 @@ class Color(commands.Cog): await ctx.send(embed=wrong_mode_embed) return - async def color_embed( - self, - ctx: commands.Context, - rgb_color: tuple[int, int, int], - color_name: str = None - ) -> None: - """Take a RGB color tuple, create embed, and send.""" - (r, g, b) = rgb_color - discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) - all_colors = self.get_color_fields(rgb_color) - hex_color = all_colors[1]["value"].replace("» hex ", "") - if color_name is None: - logger.debug(f"Find color name from hex color: {hex_color}") - color_name = self.match_color_hex(hex_color) + @staticmethod + def tuple_create(input_color: str) -> tuple[int, int, int]: + """ + Create a tuple of integers based on user's input. - async with ctx.typing(): - main_embed = Embed( - title=color_name, - description='(Approx..)', - color=discord_rgb_int, + Can handle inputs of the types: + (100, 100, 100) + 100, 100, 100 + 100 100 100 + """ + if "(" in input_color: + remove = "[() ]" + color_tuple = re.sub(remove, "", input_color) + color_tuple = tuple(map(int, color_tuple.split(","))) + elif "," in input_color: + color_tuple = tuple(map(int, input_color.split(","))) + else: + color_tuple = tuple(map(int, input_color.split(" "))) + return color_tuple + + async def hex_to_rgb(self, ctx: commands.Context, hex_string: str) -> None: + """Function to convert hex color to rgb color and send main embed.""" + hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", hex_string) + if hex_match: + if "#" in hex_string: + rgb_color = ImageColor.getcolor(hex_string, "RGB") + elif "0x" in hex_string: + hex_ = hex_string.replace("0x", "#") + rgb_color = ImageColor.getcolor(hex_, "RGB") + else: + hex_ = "#" + hex_string + rgb_color = ImageColor.getcolor(hex_, "RGB") + self.color_embed(rgb_color) + else: + await ctx.send( + embed=Embed( + title="There was an issue converting the hex color code.", + description=ERROR_MSG.format(user_color=hex_string), + ) ) - file = await self.create_thumbnail_attachment(rgb_color) - main_embed.set_thumbnail(url="attachment://color.png") - fields = self.get_color_fields(rgb_color) + def hsv_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsv color to rgb color and send main embed.""" + input_color = self.tuple_create(input_color) + (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped + h = h / 360 + s = s / 100 + v = v / 100 + rgb_color = colorsys.hsv_to_rgb(h, s, v) + (r, g, b) = rgb_color + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + self.color_embed((r, g, b)) - for field in fields: - main_embed.add_field( - name=field['name'], - value=field['value'], - inline=False, - ) + def hsl_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsl color to rgb color and send main embed.""" + input_color = self.tuple_create(input_color) + (h, s, l) = input_color + h = h / 360 + s = s / 100 + l = l / 100 # noqa: E741 It's little `L`, Reason: To maintain consistency. + rgb_color = colorsys.hls_to_rgb(h, l, s) + (r, g, b) = rgb_color + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + self.color_embed((r, g, b)) - await ctx.send(file=file, embed=main_embed) + def cmyk_to_rgb(self, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: + """Function to convert cmyk color to rgb color and send main embed.""" + input_color = self.tuple_create(input_color) + c = input_color[0] + m = input_color[1] + y = input_color[2] + k = input_color[3] + r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) + g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) + b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) + self.color_embed((r, g, b)) @staticmethod - async def create_thumbnail_attachment(color: str) -> File: - """Generate a thumbnail from `color`.""" + async def create_thumbnail_attachment(color: tuple[int, int, int]) -> File: + """ + Generate a thumbnail from `color`. + + Assumes that color is an rgb tuple. + """ thumbnail = Image.new("RGB", (100, 100), color=color) bufferedio = BytesIO() thumbnail.save(bufferedio, format="PNG") @@ -249,86 +300,40 @@ class Color(commands.Cog): color_name = "No color name match found." return color_name - @staticmethod - def tuple_create(input_color: str) -> tuple[int, int, int]: - """ - Create a tuple of integers based on user's input. - - Can handle inputs of the types: - (100, 100, 100) - 100, 100, 100 - 100 100 100 - """ - if "(" in input_color: - remove = "[() ]" - color_tuple = re.sub(remove, "", input_color) - color_tuple = tuple(map(int, color_tuple.split(","))) - elif "," in input_color: - color_tuple = tuple(map(int, input_color.split(","))) - else: - color_tuple = tuple(map(int, input_color.split(" "))) - return color_tuple - - def hsv_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: - """Function to convert hsv color to rgb color and send main embed..""" - input_color = self.tuple_create(input_color) - (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped - h = h / 360 - s = s / 100 - v = v / 100 - rgb_color = colorsys.hsv_to_rgb(h, s, v) + async def color_embed( + self, + ctx: commands.Context, + rgb_color: tuple[int, int, int], + color_name: str = None + ) -> None: + """Take a RGB color tuple, create embed, and send.""" (r, g, b) = rgb_color - r = int(r * 255) - g = int(g * 255) - b = int(b * 255) - self.color_embed((r, g, b)) + discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) + all_colors = self.get_color_fields(rgb_color) + hex_color = all_colors[1]["value"].replace("» hex ", "") + if color_name is None: + logger.debug(f"Find color name from hex color: {hex_color}") + color_name = self.match_color_hex(hex_color) - def hsl_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: - """Function to convert hsl color to rgb color and send main embed..""" - input_color = self.tuple_create(input_color) - (h, s, l) = input_color - h = h / 360 - s = s / 100 - l = l / 100 # noqa: E741 It's little `L`, Reason: To maintain consistency. - rgb_color = colorsys.hls_to_rgb(h, l, s) - (r, g, b) = rgb_color - r = int(r * 255) - g = int(g * 255) - b = int(b * 255) - self.color_embed((r, g, b)) + async with ctx.typing(): + main_embed = Embed( + title=color_name, + description='(Approx..)', + color=discord_rgb_int, + ) - def cmyk_to_rgb(self, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: - """Function to convert cmyk color to rgb color and send main embed..""" - input_color = self.tuple_create(input_color) - c = input_color[0] - m = input_color[1] - y = input_color[2] - k = input_color[3] - r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) - g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) - b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) - self.color_embed((r, g, b)) + file = await self.create_thumbnail_attachment(rgb_color) + main_embed.set_thumbnail(url="attachment://color.png") + fields = self.get_color_fields(rgb_color) - async def hex_to_rgb(self, ctx: commands.Context, hex_string: str) -> None: - """Create rgb color from hex string and send main embed.""" - hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", hex_string) - if hex_match: - if "#" in hex_string: - rgb_color = ImageColor.getcolor(hex_string, "RGB") - elif "0x" in hex_string: - hex_ = hex_string.replace("0x", "#") - rgb_color = ImageColor.getcolor(hex_, "RGB") - else: - hex_ = "#" + hex_string - rgb_color = ImageColor.getcolor(hex_, "RGB") - self.color_embed(rgb_color) - else: - await ctx.send( - embed=Embed( - title="There was an issue converting the hex color code.", - description=ERROR_MSG.format(user_color=hex_string), + for field in fields: + main_embed.add_field( + name=field['name'], + value=field['value'], + inline=False, ) - ) + + await ctx.send(file=file, embed=main_embed) def setup(bot: Bot) -> None: -- cgit v1.2.3 From 8a9d71b77a6385bd2dbf8388a732e980fe66dac2 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 22 Sep 2021 09:39:37 -0400 Subject: fix: remove `get_color_fields` call in color_embed --- bot/exts/utilities/color.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 67068809..5cdc5083 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -324,9 +324,8 @@ class Color(commands.Cog): file = await self.create_thumbnail_attachment(rgb_color) main_embed.set_thumbnail(url="attachment://color.png") - fields = self.get_color_fields(rgb_color) - for field in fields: + for field in all_colors: main_embed.add_field( name=field['name'], value=field['value'], -- cgit v1.2.3 From bacd5119f59c8d425ba9f129aff9191c98063a67 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 22 Sep 2021 09:50:42 -0400 Subject: chore: small code fixes and cleanup --- bot/exts/utilities/color.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 5cdc5083..e0398e02 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -29,7 +29,6 @@ with open(COLOR_JSON_PATH) as f: COLOR_MAPPING = json.load(f) -# define color command class Color(commands.Cog): """User initiated commands to receive color information.""" @@ -45,7 +44,6 @@ class Color(commands.Cog): """ logger.debug(f"{mode = }") logger.debug(f"{user_color = }") - color_name = None if mode.lower() == "hex": self.hex_to_rgb(ctx, user_color) elif mode.lower() == "rgb": @@ -235,11 +233,6 @@ class Color(commands.Cog): l = round(l * 100) # noqa: E741 It's little `L`, Reason: To maintain consistency. return h, s, l - hex_color = _rgb_to_hex(rgb_color) - cmyk_color = _rgb_to_cmyk(rgb_color) - hsv_color = _rgb_to_hsv(rgb_color) - hsl_color = _rgb_to_hsl(rgb_color) - all_fields = [ { "name": "RGB", @@ -247,19 +240,19 @@ class Color(commands.Cog): }, { "name": "HEX", - "value": f"» hex {hex_color}" + "value": f"» hex {_rgb_to_hex(rgb_color)}" }, { "name": "CMYK", - "value": f"» cmyk {cmyk_color}" + "value": f"» cmyk {_rgb_to_cmyk(rgb_color)}" }, { "name": "HSV", - "value": f"» hsv {hsv_color}" + "value": f"» hsv {_rgb_to_hsv(rgb_color)}" }, { "name": "HSL", - "value": f"» hsl {hsl_color}" + "value": f"» hsl {_rgb_to_hsl(rgb_color)}" }, ] @@ -277,11 +270,11 @@ class Color(commands.Cog): logger.debug(f"{match = }, {certainty = }") hex_match = COLOR_MAPPING[match] logger.debug(f"{hex_match = }") - return match, hex_match except TypeError: match = "No color name match found." hex_match = input_color_name - return match, hex_match + + return match, hex_match @staticmethod def match_color_hex(input_hex_color: str) -> str: @@ -295,10 +288,10 @@ class Color(commands.Cog): logger.debug(f"{match = }, {certainty = }") color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0] logger.debug(f"{color_name = }") - return color_name except TypeError: color_name = "No color name match found." - return color_name + + return color_name async def color_embed( self, -- cgit v1.2.3 From 23cf7cdd40dc162727e0697ccc6f802a84e9a7e0 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 23 Sep 2021 00:22:10 -0400 Subject: chore: create subcommands for sending embed --- bot/exts/utilities/color.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index e0398e02..6cc03c9a 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -45,23 +45,23 @@ class Color(commands.Cog): logger.debug(f"{mode = }") logger.debug(f"{user_color = }") if mode.lower() == "hex": - self.hex_to_rgb(ctx, user_color) + await self.hex_to_rgb(ctx, user_color) elif mode.lower() == "rgb": rgb_color = self.tuple_create(user_color) - self.color_embed(ctx, rgb_color) + await self.color_embed(ctx, rgb_color) elif mode.lower() == "hsv": - self.hsv_to_rgb(ctx, user_color) + await self.hsv_to_rgb(ctx, user_color) elif mode.lower() == "hsl": - self.hsl_to_rgb(ctx, user_color) + await self.hsl_to_rgb(ctx, user_color) elif mode.lower() == "cmyk": - self.cmyk_to_rgb(ctx, user_color) + await self.cmyk_to_rgb(ctx, user_color) elif mode.lower() == "name": color_name, hex_color = self.match_color_name(user_color) if "#" in hex_color: rgb_color = ImageColor.getcolor(hex_color, "RGB") else: rgb_color = ImageColor.getcolor("#" + hex_color, "RGB") - self.color_embed(ctx, rgb_color, color_name) + await self.color_embed(ctx, rgb_color, color_name) else: # mode is either None or an invalid code if mode is None: @@ -112,7 +112,7 @@ class Color(commands.Cog): else: hex_ = "#" + hex_string rgb_color = ImageColor.getcolor(hex_, "RGB") - self.color_embed(rgb_color) + await self.color_embed(ctx, rgb_color) else: await ctx.send( embed=Embed( @@ -121,7 +121,7 @@ class Color(commands.Cog): ) ) - def hsv_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + async def hsv_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int]) -> tuple[int, int, int]: """Function to convert hsv color to rgb color and send main embed.""" input_color = self.tuple_create(input_color) (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped @@ -133,9 +133,9 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - self.color_embed((r, g, b)) + await self.color_embed(ctx, (r, g, b)) - def hsl_to_rgb(self, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + async def hsl_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int]) -> tuple[int, int, int]: """Function to convert hsl color to rgb color and send main embed.""" input_color = self.tuple_create(input_color) (h, s, l) = input_color @@ -147,9 +147,9 @@ class Color(commands.Cog): r = int(r * 255) g = int(g * 255) b = int(b * 255) - self.color_embed((r, g, b)) + await self.color_embed(ctx, (r, g, b)) - def cmyk_to_rgb(self, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: + async def cmyk_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: """Function to convert cmyk color to rgb color and send main embed.""" input_color = self.tuple_create(input_color) c = input_color[0] @@ -159,7 +159,7 @@ class Color(commands.Cog): r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) - self.color_embed((r, g, b)) + await self.color_embed(ctx, (r, g, b)) @staticmethod async def create_thumbnail_attachment(color: tuple[int, int, int]) -> File: -- cgit v1.2.3 From 8ff23478fb2bd9cfba34686be68513ab3ac7b905 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Thu, 23 Sep 2021 07:48:45 -0400 Subject: chore: make cmyk_to_rgb def multiline --- bot/exts/utilities/color.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 6cc03c9a..9e2af325 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -149,7 +149,11 @@ class Color(commands.Cog): b = int(b * 255) await self.color_embed(ctx, (r, g, b)) - async def cmyk_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int, int]) -> tuple[int, int, int]: + async def cmyk_to_rgb( + self, + ctx: commands.Context, + input_color: tuple[int, int, int, int] + ) -> tuple[int, int, int]: """Function to convert cmyk color to rgb color and send main embed.""" input_color = self.tuple_create(input_color) c = input_color[0] -- cgit v1.2.3 From 5907900a80ce7bdeb6ddb97c80dbdd4defe0bb17 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Mon, 27 Sep 2021 09:49:42 -0400 Subject: chore: remove doubled new line in ERROR_MSG --- bot/exts/utilities/color.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 9e2af325..7c7f4bba 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -17,11 +17,11 @@ logger = logging.getLogger(__name__) ERROR_MSG = """The color code {user_color} is not a possible color combination. -\nThe range of possible values are: -\nRGB & HSV: 0-255 -\nCMYK: 0-100% -\nHSL: 0-360 degrees -\nHex: #000000-#FFFFFF +The range of possible values are: +RGB & HSV: 0-255 +CMYK: 0-100% +HSL: 0-360 degrees +Hex: #000000-#FFFFFF """ COLOR_JSON_PATH = "bot/resources/utilities/ryanzec_colours.json" -- cgit v1.2.3 From a78731ad2cf457b25d311460578a778070900385 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Mon, 27 Sep 2021 09:56:33 -0400 Subject: chore: remove single-use constant for json path --- bot/exts/utilities/color.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 7c7f4bba..ac7b5fc6 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -24,8 +24,7 @@ HSL: 0-360 degrees Hex: #000000-#FFFFFF """ -COLOR_JSON_PATH = "bot/resources/utilities/ryanzec_colours.json" -with open(COLOR_JSON_PATH) as f: +with open("bot/resources/utilities/ryanzec_colours.json") as f: COLOR_MAPPING = json.load(f) -- cgit v1.2.3 From 9699e3a62d03d27acaac6e5376c2327cb8abf739 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 23 Sep 2021 21:42:24 -0400 Subject: chore: set thumbnail image to 80x80 --- bot/exts/utilities/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index ac7b5fc6..57510488 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -171,7 +171,7 @@ class Color(commands.Cog): Assumes that color is an rgb tuple. """ - thumbnail = Image.new("RGB", (100, 100), color=color) + thumbnail = Image.new("RGB", (80, 80), color=color) bufferedio = BytesIO() thumbnail.save(bufferedio, format="PNG") bufferedio.seek(0) -- cgit v1.2.3 From 11c1c25a4767086c92b7a438aaee09fb65dcfdf1 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Mon, 27 Sep 2021 10:46:08 -0400 Subject: chore: remove single-use constant for json path --- bot/exts/utilities/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 57510488..6aa0c3cd 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -65,7 +65,7 @@ class Color(commands.Cog): # mode is either None or an invalid code if mode is None: no_mode_embed = Embed( - title="No 'mode' was passed, please define a color code.", + title="No mode was passed, please define a color code.", description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.", color=Colours.soft_red, ) -- cgit v1.2.3 From 60b146f9b55af5688b96534884ab350f63da1e28 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Fri, 24 Sep 2021 12:13:26 +0100 Subject: Add a 2 minute cooldown to the topic command Using the command while it's on cooldown will hit the error handler, which sends an error message showing how long is left on the cooldown, which is deleted after 7.5 seconds. --- bot/exts/utilities/conversationstarters.py | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/conversationstarters.py b/bot/exts/utilities/conversationstarters.py index dd537022..07d71f15 100644 --- a/bot/exts/utilities/conversationstarters.py +++ b/bot/exts/utilities/conversationstarters.py @@ -36,32 +36,16 @@ class ConvoStarters(commands.Cog): """General conversation topics.""" @commands.command() + @commands.cooldown(1, 60*2, commands.BucketType.channel) @whitelist_override(channels=ALL_ALLOWED_CHANNELS) async def topic(self, ctx: commands.Context) -> None: """ Responds with a random topic to start a conversation. - If in a Python channel, a python-related topic will be given. - - Otherwise, a random conversation topic will be received by the user. + Allows the refresh of a topic by pressing an emoji. """ - # No matter what, the form will be shown. - embed = Embed(description=f"Suggest more topics [here]({SUGGESTION_FORM})!", color=Color.blurple()) - - try: - # Fetching topics. - channel_topics = TOPICS[ctx.channel.id] - - # If the channel isn't Python-related. - except KeyError: - embed.title = f"**{next(TOPICS['default'])}**" - - # If the channel ID doesn't have any topics. - else: - embed.title = f"**{next(channel_topics)}**" - - finally: - await ctx.send(embed=embed) + message = await ctx.send(embed=self._build_topic_embed(ctx.channel.id)) + self.bot.loop.create_task(self._listen_for_refresh(message)) def setup(bot: Bot) -> None: -- cgit v1.2.3 From 515c6390563f33a02af2e46fe6f3d13b15353a0a Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Fri, 24 Sep 2021 13:10:53 +0100 Subject: Allow topics to be refreshed This is done via an emoji as buttons are too big Co-authored-by: Bluenix --- bot/exts/utilities/conversationstarters.py | 65 ++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/conversationstarters.py b/bot/exts/utilities/conversationstarters.py index 07d71f15..5d62fa83 100644 --- a/bot/exts/utilities/conversationstarters.py +++ b/bot/exts/utilities/conversationstarters.py @@ -1,11 +1,14 @@ +import asyncio +from contextlib import suppress +from functools import partial from pathlib import Path +import discord import yaml -from discord import Color, Embed from discord.ext import commands from bot.bot import Bot -from bot.constants import WHITELISTED_CHANNELS +from bot.constants import MODERATION_ROLES, WHITELISTED_CHANNELS from bot.utils.decorators import whitelist_override from bot.utils.randomization import RandomCycle @@ -35,6 +38,62 @@ TOPICS = { class ConvoStarters(commands.Cog): """General conversation topics.""" + def __init__(self, bot: Bot): + self.bot = bot + + @staticmethod + def _build_topic_embed(channel_id: int) -> discord.Embed: + """ + Build an embed containing a conversation topic. + + If in a Python channel, a python-related topic will be given. + Otherwise, a random conversation topic will be received by the user. + """ + # No matter what, the form will be shown. + embed = discord.Embed( + description=f"Suggest more topics [here]({SUGGESTION_FORM})!", + color=discord.Color.blurple() + ) + + try: + channel_topics = TOPICS[channel_id] + except KeyError: + # Channel doesn't have any topics. + embed.title = f"**{next(TOPICS['default'])}**" + else: + embed.title = f"**{next(channel_topics)}**" + return embed + + def _predicate(self, message: discord.Message, reaction: discord.Reaction, user: discord.User) -> bool: + right_reaction = ( + user != self.bot.user + and reaction.message.id == message.id + and str(reaction.emoji) == "🔄" + ) + if not right_reaction: + return False + + is_moderator = any(role.id in MODERATION_ROLES for role in getattr(user, "roles", [])) + if is_moderator or user.id == message.author.id: + return True + + return False + + async def _listen_for_refresh(self, message: discord.Message) -> None: + await message.add_reaction("🔄") + while True: + try: + reaction, user = await self.bot.wait_for( + "reaction_add", + check=partial(self._predicate, message), + timeout=60.0 + ) + except asyncio.TimeoutError: + with suppress(discord.NotFound): + await message.clear_reaction("🔄") + else: + await message.edit(embed=self._build_topic_embed(message.channel.id)) + @commands.command() @commands.cooldown(1, 60*2, commands.BucketType.channel) @whitelist_override(channels=ALL_ALLOWED_CHANNELS) @@ -50,4 +109,4 @@ class ConvoStarters(commands.Cog): def setup(bot: Bot) -> None: """Load the ConvoStarters cog.""" - bot.add_cog(ConvoStarters()) + bot.add_cog(ConvoStarters(bot)) -- cgit v1.2.3 From 999604b335840fe820deddd0cebea7b6b601c218 Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 8 Oct 2021 11:39:20 +0100 Subject: `.topic` command improvements. - Fix bug where command author couldn't re-roll - Now removes user's reaction up re-roll - Added a missing `break` statement --- bot/exts/utilities/conversationstarters.py | 45 ++++++++++++++++++------------ 1 file changed, 27 insertions(+), 18 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/conversationstarters.py b/bot/exts/utilities/conversationstarters.py index 5d62fa83..2316c50d 100644 --- a/bot/exts/utilities/conversationstarters.py +++ b/bot/exts/utilities/conversationstarters.py @@ -2,6 +2,7 @@ import asyncio from contextlib import suppress from functools import partial from pathlib import Path +from typing import Union import discord import yaml @@ -64,35 +65,43 @@ class ConvoStarters(commands.Cog): embed.title = f"**{next(channel_topics)}**" return embed - def _predicate(self, message: discord.Message, reaction: discord.Reaction, user: discord.User) -> bool: - right_reaction = ( - user != self.bot.user - and reaction.message.id == message.id - and str(reaction.emoji) == "🔄" - ) - if not right_reaction: - return False - - is_moderator = any(role.id in MODERATION_ROLES for role in getattr(user, "roles", [])) - if is_moderator or user.id == message.author.id: - return True - - return False - - async def _listen_for_refresh(self, message: discord.Message) -> None: + @staticmethod + def _predicate( + command_invoker: Union[discord.User, discord.Member], + message: discord.Message, + reaction: discord.Reaction, + user: discord.User + ) -> bool: + user_is_moderator = any(role.id in MODERATION_ROLES for role in getattr(user, "roles", [])) + user_is_invoker = user.id == command_invoker.id + + is_right_reaction = all(( + reaction.message.id == message.id, + str(reaction.emoji) == "🔄", + user_is_moderator or user_is_invoker + )) + return is_right_reaction + + async def _listen_for_refresh( + self, + command_invoker: Union[discord.User, discord.Member], + message: discord.Message + ) -> None: await message.add_reaction("🔄") while True: try: reaction, user = await self.bot.wait_for( "reaction_add", - check=partial(self._predicate, message), + check=partial(self._predicate, command_invoker, message), timeout=60.0 ) except asyncio.TimeoutError: with suppress(discord.NotFound): await message.clear_reaction("🔄") + break else: await message.edit(embed=self._build_topic_embed(message.channel.id)) + await message.remove_reaction(reaction, user) @commands.command() @commands.cooldown(1, 60*2, commands.BucketType.channel) @@ -104,7 +113,7 @@ class ConvoStarters(commands.Cog): Allows the refresh of a topic by pressing an emoji. """ message = await ctx.send(embed=self._build_topic_embed(ctx.channel.id)) - self.bot.loop.create_task(self._listen_for_refresh(message)) + self.bot.loop.create_task(self._listen_for_refresh(ctx.author, message)) def setup(bot: Bot) -> None: -- cgit v1.2.3 From 3c26b4d2fc4746da13695c31ef4dc7435f35525f Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 8 Oct 2021 14:24:14 +0100 Subject: Add handling for `discord.NotFound` when re-rolling / removing reaction --- bot/exts/utilities/conversationstarters.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/conversationstarters.py b/bot/exts/utilities/conversationstarters.py index 2316c50d..fcb5f977 100644 --- a/bot/exts/utilities/conversationstarters.py +++ b/bot/exts/utilities/conversationstarters.py @@ -100,8 +100,13 @@ class ConvoStarters(commands.Cog): await message.clear_reaction("🔄") break else: - await message.edit(embed=self._build_topic_embed(message.channel.id)) - await message.remove_reaction(reaction, user) + try: + await message.edit(embed=self._build_topic_embed(message.channel.id)) + except discord.NotFound: + break + + with suppress(discord.NotFound): + await message.remove_reaction(reaction, user) @commands.command() @commands.cooldown(1, 60*2, commands.BucketType.channel) -- cgit v1.2.3 From e01503a61015d483cd70e1e408c647b4054a927f Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 8 Oct 2021 14:27:15 +0100 Subject: Remove unnecessary `else` --- bot/exts/utilities/conversationstarters.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/conversationstarters.py b/bot/exts/utilities/conversationstarters.py index fcb5f977..dcbfe4d5 100644 --- a/bot/exts/utilities/conversationstarters.py +++ b/bot/exts/utilities/conversationstarters.py @@ -99,14 +99,14 @@ class ConvoStarters(commands.Cog): with suppress(discord.NotFound): await message.clear_reaction("🔄") break - else: - try: - await message.edit(embed=self._build_topic_embed(message.channel.id)) - except discord.NotFound: - break - with suppress(discord.NotFound): - await message.remove_reaction(reaction, user) + try: + await message.edit(embed=self._build_topic_embed(message.channel.id)) + except discord.NotFound: + break + + with suppress(discord.NotFound): + await message.remove_reaction(reaction, user) @commands.command() @commands.cooldown(1, 60*2, commands.BucketType.channel) -- cgit v1.2.3 From a0c86e72c0a418f1265a1aa035b45048d8e921ec Mon Sep 17 00:00:00 2001 From: Shom770 <82843611+Shom770@users.noreply.github.com> Date: Wed, 13 Oct 2021 17:23:30 -0400 Subject: Challenges (#860) * beginning commit creating the base of the hangman, code needs to be linted in the future * updated words list * adding images to show the hangman person * added images, though it is a bit laggy * replacing images with discord attachment urls * adding error if filters aren't found * fixing typo in ``filter_not_found_embed`` * final lints + removing `mode` parameter as it renders useless * linting flake8 errors * adding newline at the end of `top_1000_used_words.txt` * minor change to filter message * beginning commit -- trying to add bs4 to pyproject.toml, though it is currently failing * kata information section done, ready for issue * fixing bugs with the query not being fully picked up, also allowing query only with no kyu * fixing bug where user cannot leave all arguments blank * typo - forgot unary before the level within the `language and not query` if statement * changing to random kata chosen * ensuring that if the user provides a query that won't work, that it won't error out * limiting choice to smaller numbers if a query was provided, so the user gets what they want * improving hangman docstring * removing `bot/resources/evergreen/hangman` directory as file attachments are used * replacing single quotes with double quotes, to adhere to the style guide. * fixing style inconsistencies and other problems with how the code looks - as per requested by Objectivix * fixing `IMAGES` style inconsistency * adding trailing commas and switching to `Colours` for consistency * adding trailing commas and switching to `Colours` for consistency * fixing the remnants of non-trailing commas and allowing specification for single player vs mulitplayer * removing all 2 letter words from the hangman word choosing and removing words that @Objectivix found that shouldn't be in the list of words * removing some inappropriate words from the txt file * Adding space for grammatical errors Co-authored-by: ChrisJL * changing two periods to a full stop & wrapping try and except block to only the part that can raise it * using negative replies instead along with fixing grammatical errors in the sentence * removing words that could be considered inappropirate * removing `TOP_WORDS_FILE_PATH` and making `ALL_WORDS` a global variable. * error handling * fixing the overcomplication of the bs4 portion * adding button and dropdowns to the challenges command * more specific docstring * more specific docstring * finishing dropdowns/buttons * putting the dropdown on top of the link button * replacing ' with a double quote for some strings * Removing more words The words removed shouldn't really belong here * Update bot/exts/utilities/challenges.py Co-authored-by: Bluenix * replacing mapping_of_images with IMAGES and other fixes * Dedenting Co-authored-by: Bluenix * Improving tries logic Co-authored-by: Bluenix * Updating `positions` list to set Co-authored-by: Bluenix * Updating setup docstring Co-authored-by: Bluenix * Updating comment in callback function of the dropdown Co-authored-by: Bluenix * fixing too many blank lines * Hardcode dictionary Co-authored-by: Bluenix * restructuring * fixing errors * Remove unnecessary comments Co-authored-by: Bluenix * Remove unnecessary comments Co-authored-by: Bluenix * Improve comment explanation Co-authored-by: Bluenix * Remove redundant extra membership test Co-authored-by: Bluenix * Removing verbose variable definition Co-authored-by: Bluenix * Redundant list Co-authored-by: Bluenix * Shorten 'social distancing' (too many separations) between related lines Co-authored-by: Bluenix * improving docstring in `kata_id` * sending embed if error occurs with api or bs4, also hardcoding params dictionary * Better comments Co-authored-by: Bluenix * better docstring Co-authored-by: Bluenix * Removing f-string inception and replacing it with more readable code Co-authored-by: Bluenix * More specific docstring Co-authored-by: Bluenix * Removing redundant comments Co-authored-by: Bluenix * Fixing linting errors * mapping of kyu -> constant * adding trailing comma * specific comment regarding where colors are from for `MAPPING_OF_KYU` * changing name to link too along with link button * adding ellipsis to make it more clear for `Read more` * removing redundant sentences from all docstrings of embed creator functions * fixing unboundlocalerror due to kata_url only being defined under a certain condition * only allowing supported languages on codewars.com * fixing url glitch with embed * Delete hangman.py * Delete top_1000_used_words.txt * hangman dependencies leaked into this PR, removing them * add bs4 and lxml back to lock file * Capitalize comments Co-authored-by: Bluenix * Improving comments (capitalization) Co-authored-by: Bluenix * polishing * explaining that self.original_message will be set later in the callback function of the dropdown * fixing nitpicks * cast to integer from hex * removing unnecessary trailing commas * Simplifying L274-L276 Co-authored-by: Bluenix * Add ellipsis to end of description if it's too long Co-authored-by: Bluenix * Changing to hex Co-authored-by: Bluenix * Running blocking function (BeautifulSoup.find_all) to thread Co-authored-by: Bluenix * logger.error errors * Fixing error with to_thread * Fixing errors with MAPPING_OF_KYU Co-authored-by: Bluenix * changing `query` to `-query` if the query is a kata level * changing embed names to add the kata name * Mimicking mailing list's behavior Co-authored-by: Bluenix * url attribute for all embeds & title for all embeds * remove view after a certain amount of tikme * disabling view after waiting instead of just editing it out * styling * remove view to avoid spamming errors * changing `logger` to `log` Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> * Change `logger` to `log` for logging errors Co-authored-by: ChrisJL Co-authored-by: Bluenix Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/utilities/challenges.py | 335 +++++++++++++++++++++++++++++++++++++++ poetry.lock | 98 +++++++++++- pyproject.toml | 2 + 3 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 bot/exts/utilities/challenges.py (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/challenges.py b/bot/exts/utilities/challenges.py new file mode 100644 index 00000000..234eb0be --- /dev/null +++ b/bot/exts/utilities/challenges.py @@ -0,0 +1,335 @@ +import logging +from asyncio import to_thread +from random import choice +from typing import Union + +from bs4 import BeautifulSoup +from discord import Embed, Interaction, SelectOption, ui +from discord.ext import commands + +from bot.bot import Bot +from bot.constants import Colours, Emojis, NEGATIVE_REPLIES + +log = logging.getLogger(__name__) +API_ROOT = "https://www.codewars.com/api/v1/code-challenges/{kata_id}" + +# Map difficulty for the kata to color we want to display in the embed. +# These colors are representative of the colors that each kyu's level represents on codewars.com +MAPPING_OF_KYU = { + 8: 0xdddbda, 7: 0xdddbda, 6: 0xecb613, 5: 0xecb613, + 4: 0x3c7ebb, 3: 0x3c7ebb, 2: 0x866cc7, 1: 0x866cc7 +} + +# Supported languages for a kata on codewars.com +SUPPORTED_LANGUAGES = { + "stable": [ + "c", "c#", "c++", "clojure", "coffeescript", "coq", "crystal", "dart", "elixir", + "f#", "go", "groovy", "haskell", "java", "javascript", "kotlin", "lean", "lua", "nasm", + "php", "python", "racket", "ruby", "rust", "scala", "shell", "sql", "swift", "typescript" + ], + "beta": [ + "agda", "bf", "cfml", "cobol", "commonlisp", "elm", "erlang", "factor", + "forth", "fortran", "haxe", "idris", "julia", "nim", "objective-c", "ocaml", + "pascal", "perl", "powershell", "prolog", "purescript", "r", "raku", "reason", "solidity", "vb.net" + ] +} + + +class InformationDropdown(ui.Select): + """A dropdown inheriting from ui.Select that allows finding out other information about the kata.""" + + def __init__(self, language_embed: Embed, tags_embed: Embed, other_info_embed: Embed, main_embed: Embed): + options = [ + SelectOption( + label="Main Information", + description="See the kata's difficulty, description, etc.", + emoji="🌎" + ), + SelectOption( + label="Languages", + description="See what languages this kata supports!", + emoji=Emojis.reddit_post_text + ), + SelectOption( + label="Tags", + description="See what categories this kata falls under!", + emoji=Emojis.stackoverflow_tag + ), + SelectOption( + label="Other Information", + description="See how other people performed on this kata and more!", + emoji="ℹ" + ) + ] + + # We map the option label to the embed instance so that it can be easily looked up later in O(1) + self.mapping_of_embeds = { + "Main Information": main_embed, + "Languages": language_embed, + "Tags": tags_embed, + "Other Information": other_info_embed, + } + + super().__init__( + placeholder="See more information regarding this kata", + min_values=1, + max_values=1, + options=options + ) + + async def callback(self, interaction: Interaction) -> None: + """Callback for when someone clicks on a dropdown.""" + # Edit the message to the embed selected in the option + # The `original_message` attribute is set just after the message is sent with the view. + # The attribute is not set during initialization. + result_embed = self.mapping_of_embeds[self.values[0]] + await self.original_message.edit(embed=result_embed) + + +class Challenges(commands.Cog): + """ + Cog for the challenge command. + + The challenge command pulls a random kata from codewars.com. + A kata is the name for a challenge, specific to codewars.com. + + The challenge command also has filters to customize the kata that is given. + You can specify the language the kata should be from, difficulty and topic of the kata. + """ + + def __init__(self, bot: Bot): + self.bot = bot + + async def kata_id(self, search_link: str, params: dict) -> Union[str, Embed]: + """ + Uses bs4 to get the HTML code for the page of katas, where the page is the link of the formatted `search_link`. + + This will webscrape the search page with `search_link` and then get the ID of a kata for the + codewars.com API to use. + """ + async with self.bot.http_session.get(search_link, params=params) as response: + if response.status != 200: + error_embed = Embed( + title=choice(NEGATIVE_REPLIES), + description="We ran into an error when getting the kata from codewars.com, try again later.", + color=Colours.soft_red + ) + log.error(f"Unexpected response from codewars.com, status code: {response.status}") + return error_embed + + soup = BeautifulSoup(await response.text(), features="lxml") + first_kata_div = await to_thread(soup.find_all, "div", class_="item-title px-0") + + if not first_kata_div: + raise commands.BadArgument("No katas could be found with the filters provided.") + elif len(first_kata_div) >= 3: + first_kata_div = choice(first_kata_div[:3]) + elif "q=" not in search_link: + first_kata_div = choice(first_kata_div) + else: + first_kata_div = first_kata_div[0] + + # There are numerous divs before arriving at the id of the kata, which can be used for the link. + first_kata_id = first_kata_div.a["href"].split("/")[-1] + return first_kata_id + + async def kata_information(self, kata_id: str) -> Union[dict, Embed]: + """ + Returns the information about the Kata. + + Uses the codewars.com API to get information about the kata using `kata_id`. + """ + async with self.bot.http_session.get(API_ROOT.format(kata_id=kata_id)) as response: + if response.status != 200: + error_embed = Embed( + title=choice(NEGATIVE_REPLIES), + description="We ran into an error when getting the kata information, try again later.", + color=Colours.soft_red + ) + log.error(f"Unexpected response from codewars.com/api/v1, status code: {response.status}") + return error_embed + + return await response.json() + + @staticmethod + def main_embed(kata_information: dict) -> Embed: + """Creates the main embed which displays the name, difficulty and description of the kata.""" + kata_description = kata_information["description"] + kata_url = f"https://codewars.com/kata/{kata_information['id']}" + + # Ensuring it isn't over the length 1024 + if len(kata_description) > 1024: + kata_description = "\n".join(kata_description[:1000].split("\n")[:-1]) + "..." + kata_description += f" [continue reading]({kata_url})" + + kata_embed = Embed( + title=kata_information["name"], + description=kata_description, + color=MAPPING_OF_KYU[int(kata_information["rank"]["name"].replace(" kyu", ""))], + url=kata_url + ) + kata_embed.add_field(name="Difficulty", value=kata_information["rank"]["name"], inline=False) + return kata_embed + + @staticmethod + def language_embed(kata_information: dict) -> Embed: + """Creates the 'language embed' which displays all languages the kata supports.""" + kata_url = f"https://codewars.com/kata/{kata_information['id']}" + + languages = "\n".join(map(str.title, kata_information["languages"])) + language_embed = Embed( + title=kata_information["name"], + description=f"```yaml\nSupported Languages:\n{languages}\n```", + color=Colours.python_blue, + url=kata_url + ) + return language_embed + + @staticmethod + def tags_embed(kata_information: dict) -> Embed: + """ + Creates the 'tags embed' which displays all the tags of the Kata. + + Tags explain what the kata is about, this is what codewars.com calls categories. + """ + kata_url = f"https://codewars.com/kata/{kata_information['id']}" + + tags = "\n".join(kata_information["tags"]) + tags_embed = Embed( + title=kata_information["name"], + description=f"```yaml\nTags:\n{tags}\n```", + color=Colours.grass_green, + url=kata_url + ) + return tags_embed + + @staticmethod + def miscellaneous_embed(kata_information: dict) -> Embed: + """ + Creates the 'other information embed' which displays miscellaneous information about the kata. + + This embed shows statistics such as the total number of people who completed the kata, + the total number of stars of the kata, etc. + """ + kata_url = f"https://codewars.com/kata/{kata_information['id']}" + + embed = Embed( + title=kata_information["name"], + description="```nim\nOther Information\n```", + color=Colours.grass_green, + url=kata_url + ) + embed.add_field( + name="`Total Score`", + value=f"```css\n{kata_information['voteScore']}\n```", + inline=False + ) + embed.add_field( + name="`Total Stars`", + value=f"```css\n{kata_information['totalStars']}\n```", + inline=False + ) + embed.add_field( + name="`Total Completed`", + value=f"```css\n{kata_information['totalCompleted']}\n```", + inline=False + ) + embed.add_field( + name="`Total Attempts`", + value=f"```css\n{kata_information['totalAttempts']}\n```", + inline=False + ) + return embed + + @staticmethod + def create_view(dropdown: InformationDropdown, link: str) -> ui.View: + """ + Creates the discord.py View for the Discord message components (dropdowns and buttons). + + The discord UI is implemented onto the embed, where the user can choose what information about the kata they + want, along with a link button to the kata itself. + """ + view = ui.View() + view.add_item(dropdown) + view.add_item(ui.Button(label="View the Kata", url=link)) + return view + + @commands.command(aliases=["kata"]) + @commands.cooldown(1, 5, commands.BucketType.user) + async def challenge(self, ctx: commands.Context, language: str = "python", *, query: str = None) -> None: + """ + The challenge command pulls a random kata (challenge) from codewars.com. + + The different ways to use this command are: + `.challenge ` - Pulls a random challenge within that language's scope. + `.challenge ` - The difficulty can be from 1-8, + 1 being the hardest, 8 being the easiest. This pulls a random challenge within that difficulty & language. + `.challenge ` - Pulls a random challenge with the query provided under the language + `.challenge , ` - Pulls a random challenge with the query provided, + under that difficulty within the language's scope. + """ + if language.lower() not in SUPPORTED_LANGUAGES["stable"] + SUPPORTED_LANGUAGES["beta"]: + raise commands.BadArgument("This is not a recognized language on codewars.com!") + + get_kata_link = f"https://codewars.com/kata/search/{language}" + params = {} + + if language and not query: + level = f"-{choice([1, 2, 3, 4, 5, 6, 7, 8])}" + params["r[]"] = level + elif "," in query: + query_splitted = query.split("," if ", " not in query else ", ") + + if len(query_splitted) > 2: + raise commands.BadArgument( + "There can only be one comma within the query, separating the difficulty and the query itself." + ) + + query, level = query_splitted + params["q"] = query + params["r[]"] = f"-{level}" + elif query.isnumeric(): + params["r[]"] = f"-{query}" + else: + params["q"] = query + + params["beta"] = str(language in SUPPORTED_LANGUAGES["beta"]).lower() + + first_kata_id = await self.kata_id(get_kata_link, params) + if isinstance(first_kata_id, Embed): + # We ran into an error when retrieving the website link + await ctx.send(embed=first_kata_id) + return + + kata_information = await self.kata_information(first_kata_id) + if isinstance(kata_information, Embed): + # Something went wrong when trying to fetch the kata information + await ctx.d(embed=kata_information) + return + + kata_embed = self.main_embed(kata_information) + language_embed = self.language_embed(kata_information) + tags_embed = self.tags_embed(kata_information) + miscellaneous_embed = self.miscellaneous_embed(kata_information) + + dropdown = InformationDropdown( + main_embed=kata_embed, + language_embed=language_embed, + tags_embed=tags_embed, + other_info_embed=miscellaneous_embed + ) + kata_view = self.create_view(dropdown, f"https://codewars.com/kata/{first_kata_id}") + original_message = await ctx.send( + embed=kata_embed, + view=kata_view + ) + dropdown.original_message = original_message + + wait_for_kata = await kata_view.wait() + if wait_for_kata: + await original_message.edit(embed=kata_embed, view=None) + + +def setup(bot: Bot) -> None: + """Load the Challenges cog.""" + bot.add_cog(Challenges(bot)) diff --git a/poetry.lock b/poetry.lock index 289f2039..21373a92 100644 --- a/poetry.lock +++ b/poetry.lock @@ -100,6 +100,21 @@ python-versions = ">=2.7" docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] +[[package]] +name = "beautifulsoup4" +version = "4.10.0" +description = "Screen-scraping library" +category = "main" +optional = false +python-versions = ">3.0.0" + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "certifi" version = "2021.5.30" @@ -173,6 +188,7 @@ voice = ["PyNaCl (>=1.3.0,<1.5)"] [package.source] type = "url" url = "https://github.com/Rapptz/discord.py/archive/45d498c1b76deaf3b394d17ccf56112fa691d160.zip" + [[package]] name = "distlib" version = "0.3.2" @@ -355,6 +371,20 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "lxml" +version = "4.6.3" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["beautifulsoup4"] +source = ["Cython (>=0.29.7)"] + [[package]] name = "matplotlib" version = "3.4.3" @@ -653,6 +683,14 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "soupsieve" +version = "2.2.1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "taskipy" version = "1.8.1" @@ -730,7 +768,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "9efbf6be5298ab8ace2588e218be309e105987bfdfa8317453d584a1faac4934" +content-hash = "6cced4e3fff83ad6ead9a18b3f585b83426fab34f6e2bcf2466c2ebbbf66dac4" [metadata.files] aiodns = [ @@ -800,6 +838,10 @@ attrs = [ {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"}, {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"}, ] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"}, + {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"}, +] certifi = [ {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, @@ -1016,6 +1058,56 @@ kiwisolver = [ {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bcadb05c3d4794eb9eee1dddf1c24215c92fb7b55a80beae7a60530a91060560"}, {file = "kiwisolver-1.3.2.tar.gz", hash = "sha256:fc4453705b81d03568d5b808ad8f09c77c47534f6ac2e72e733f9ca4714aa75c"}, ] +lxml = [ + {file = "lxml-4.6.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2"}, + {file = "lxml-4.6.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f"}, + {file = "lxml-4.6.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d"}, + {file = "lxml-4.6.3-cp27-cp27m-win32.whl", hash = "sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106"}, + {file = "lxml-4.6.3-cp27-cp27m-win_amd64.whl", hash = "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee"}, + {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f"}, + {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4"}, + {file = "lxml-4.6.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:64812391546a18896adaa86c77c59a4998f33c24788cadc35789e55b727a37f4"}, + {file = "lxml-4.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c1a40c06fd5ba37ad39caa0b3144eb3772e813b5fb5b084198a985431c2f1e8d"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16"}, + {file = "lxml-4.6.3-cp35-cp35m-win32.whl", hash = "sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2"}, + {file = "lxml-4.6.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4"}, + {file = "lxml-4.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d916d31fd85b2f78c76400d625076d9124de3e4bda8b016d25a050cc7d603f24"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617"}, + {file = "lxml-4.6.3-cp36-cp36m-win32.whl", hash = "sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04"}, + {file = "lxml-4.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a"}, + {file = "lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:36108c73739985979bf302006527cf8a20515ce444ba916281d1c43938b8bb96"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92"}, + {file = "lxml-4.6.3-cp37-cp37m-win32.whl", hash = "sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade"}, + {file = "lxml-4.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b"}, + {file = "lxml-4.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:e1cbd3f19a61e27e011e02f9600837b921ac661f0c40560eefb366e4e4fb275e"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1b38116b6e628118dea5b2186ee6820ab138dbb1e24a13e478490c7db2f326ae"}, + {file = "lxml-4.6.3-cp38-cp38-win32.whl", hash = "sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28"}, + {file = "lxml-4.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7"}, + {file = "lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3082c518be8e97324390614dacd041bb1358c882d77108ca1957ba47738d9d59"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a"}, + {file = "lxml-4.6.3-cp39-cp39-win32.whl", hash = "sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f"}, + {file = "lxml-4.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83"}, + {file = "lxml-4.6.3.tar.gz", hash = "sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468"}, +] matplotlib = [ {file = "matplotlib-3.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c988bb43414c7c2b0a31bd5187b4d27fd625c080371b463a6d422047df78913"}, {file = "matplotlib-3.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f1c5efc278d996af8a251b2ce0b07bbeccb821f25c8c9846bdcb00ffc7f158aa"}, @@ -1401,6 +1493,10 @@ sortedcontainers = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] +soupsieve = [ + {file = "soupsieve-2.2.1-py3-none-any.whl", hash = "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"}, + {file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"}, +] taskipy = [ {file = "taskipy-1.8.1-py3-none-any.whl", hash = "sha256:2b98f499966e40175d1f1306a64587f49dfa41b90d0d86c8f28b067cc58d0a56"}, {file = "taskipy-1.8.1.tar.gz", hash = "sha256:7a2404125817e45d80e13fa663cae35da6e8ba590230094e815633653e25f98f"}, diff --git a/pyproject.toml b/pyproject.toml index 7848f593..08287b23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,12 +12,14 @@ aiodns = "~=2.0" aioredis = "~1.3" rapidfuzz = "~=1.4" arrow = "~=1.1.0" +beautifulsoup4 = "~=4.9" pillow = "~=8.1" sentry-sdk = "~=0.19" PyYAML = "~=5.4" async-rediscache = {extras = ["fakeredis"], version = "~=0.1.4"} emojis = "~=0.6.0" matplotlib = "~=3.4.1" +lxml = "~=4.4" [tool.poetry.dev-dependencies] flake8 = "~=3.8" -- cgit v1.2.3 From bbef4f27a5ebade439e7073f66cac20977b18472 Mon Sep 17 00:00:00 2001 From: Shom770 <82843611+Shom770@users.noreply.github.com> Date: Wed, 13 Oct 2021 18:53:34 -0400 Subject: fixing errors --- bot/exts/utilities/challenges.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/challenges.py b/bot/exts/utilities/challenges.py index 234eb0be..ff9394ce 100644 --- a/bot/exts/utilities/challenges.py +++ b/bot/exts/utilities/challenges.py @@ -162,10 +162,16 @@ class Challenges(commands.Cog): kata_description = "\n".join(kata_description[:1000].split("\n")[:-1]) + "..." kata_description += f" [continue reading]({kata_url})" + if kata_information["rank"]["name"] is None: + embed_color = 8 + kata_information["rank"]["name"] = "Unable to retrieve difficulty for beta languages." + else: + embed_color = int(kata_information["rank"]["name"].replace(" kyu", "")) + kata_embed = Embed( title=kata_information["name"], description=kata_description, - color=MAPPING_OF_KYU[int(kata_information["rank"]["name"].replace(" kyu", ""))], + color=MAPPING_OF_KYU[embed_color], url=kata_url ) kata_embed.add_field(name="Difficulty", value=kata_information["rank"]["name"], inline=False) @@ -275,8 +281,7 @@ class Challenges(commands.Cog): params = {} if language and not query: - level = f"-{choice([1, 2, 3, 4, 5, 6, 7, 8])}" - params["r[]"] = level + pass elif "," in query: query_splitted = query.split("," if ", " not in query else ", ") -- cgit v1.2.3 From 196c451c867bc78beab2492b963b7d9a1f6a13f5 Mon Sep 17 00:00:00 2001 From: Shom770 <82843611+Shom770@users.noreply.github.com> Date: Thu, 14 Oct 2021 06:37:27 -0400 Subject: requested changes from TizzySaurus implemented --- bot/exts/utilities/challenges.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/challenges.py b/bot/exts/utilities/challenges.py index ff9394ce..d3ef82a0 100644 --- a/bot/exts/utilities/challenges.py +++ b/bot/exts/utilities/challenges.py @@ -164,9 +164,10 @@ class Challenges(commands.Cog): if kata_information["rank"]["name"] is None: embed_color = 8 - kata_information["rank"]["name"] = "Unable to retrieve difficulty for beta languages." + kata_difficulty = "Unable to retrieve difficulty for beta languages." else: embed_color = int(kata_information["rank"]["name"].replace(" kyu", "")) + kata_difficulty = kata_information["rank"]["name"] kata_embed = Embed( title=kata_information["name"], @@ -174,7 +175,7 @@ class Challenges(commands.Cog): color=MAPPING_OF_KYU[embed_color], url=kata_url ) - kata_embed.add_field(name="Difficulty", value=kata_information["rank"]["name"], inline=False) + kata_embed.add_field(name="Difficulty", value=kata_difficulty, inline=False) return kata_embed @staticmethod @@ -280,9 +281,7 @@ class Challenges(commands.Cog): get_kata_link = f"https://codewars.com/kata/search/{language}" params = {} - if language and not query: - pass - elif "," in query: + if query is not None and "," in query: query_splitted = query.split("," if ", " not in query else ", ") if len(query_splitted) > 2: @@ -293,9 +292,9 @@ class Challenges(commands.Cog): query, level = query_splitted params["q"] = query params["r[]"] = f"-{level}" - elif query.isnumeric(): + elif query is not None and query.isnumeric(): params["r[]"] = f"-{query}" - else: + elif query is not None: params["q"] = query params["beta"] = str(language in SUPPORTED_LANGUAGES["beta"]).lower() -- cgit v1.2.3 From 3ff315f34721bca59b0820207da6bdd748155293 Mon Sep 17 00:00:00 2001 From: Shom770 <82843611+Shom770@users.noreply.github.com> Date: Thu, 14 Oct 2021 06:51:23 -0400 Subject: removed repeating query is None --- bot/exts/utilities/challenges.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/challenges.py b/bot/exts/utilities/challenges.py index d3ef82a0..e4738455 100644 --- a/bot/exts/utilities/challenges.py +++ b/bot/exts/utilities/challenges.py @@ -281,21 +281,22 @@ class Challenges(commands.Cog): get_kata_link = f"https://codewars.com/kata/search/{language}" params = {} - if query is not None and "," in query: - query_splitted = query.split("," if ", " not in query else ", ") - - if len(query_splitted) > 2: - raise commands.BadArgument( - "There can only be one comma within the query, separating the difficulty and the query itself." - ) - - query, level = query_splitted - params["q"] = query - params["r[]"] = f"-{level}" - elif query is not None and query.isnumeric(): - params["r[]"] = f"-{query}" - elif query is not None: - params["q"] = query + if query is not None: + if "," in query: + query_splitted = query.split("," if ", " not in query else ", ") + + if len(query_splitted) > 2: + raise commands.BadArgument( + "There can only be one comma within the query, separating the difficulty and the query itself." + ) + + query, level = query_splitted + params["q"] = query + params["r[]"] = f"-{level}" + elif query.isnumeric(): + params["r[]"] = f"-{query}" + else: + params["q"] = query params["beta"] = str(language in SUPPORTED_LANGUAGES["beta"]).lower() -- cgit v1.2.3 From a7bb17c3e475594ac2e52a4958f382fe9d26b036 Mon Sep 17 00:00:00 2001 From: TizzySaurus <47674925+TizzySaurus@users.noreply.github.com> Date: Sun, 17 Oct 2021 12:21:09 +0100 Subject: Fix bugs in `.issue` command & add aliases - Now requires at least one issue/PR - No longer continues to send issues/PRs when there's too many listed in the invocation - Added plural aliases (`.issues` and `.prs`) --- bot/exts/utilities/issues.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/issues.py b/bot/exts/utilities/issues.py index 8a7ebed0..36655e1b 100644 --- a/bot/exts/utilities/issues.py +++ b/bot/exts/utilities/issues.py @@ -185,7 +185,7 @@ class Issues(commands.Cog): return resp @whitelist_override(channels=WHITELISTED_CHANNELS, categories=WHITELISTED_CATEGORIES) - @commands.command(aliases=("pr",)) + @commands.command(aliases=("issues", "pr", "prs")) async def issue( self, ctx: commands.Context, @@ -197,14 +197,23 @@ class Issues(commands.Cog): # Remove duplicates numbers = set(numbers) - if len(numbers) > MAXIMUM_ISSUES: - embed = discord.Embed( + err_message = None + if not numbers: + err_message = "You must have at least one issue/PR!" + + elif len(numbers) > MAXIMUM_ISSUES: + err_message = f"Too many issues/PRs! (maximum of {MAXIMUM_ISSUES})" + + # If there's an error with command invocation then send an error embed + if err_message is not None: + err_embed = discord.Embed( title=random.choice(ERROR_REPLIES), color=Colours.soft_red, - description=f"Too many issues/PRs! (maximum of {MAXIMUM_ISSUES})" + description=err_message ) - await ctx.send(embed=embed) + await ctx.send(embed=err_embed) await invoke_help_command(ctx) + return results = [await self.fetch_issues(number, repository, user) for number in numbers] await ctx.send(embed=self.format_embed(results, user, repository)) -- cgit v1.2.3 From 93f8385fcaa543b7deb69e7c7740cd148be6297c Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Thu, 21 Oct 2021 22:29:54 -0400 Subject: Add WTF Python Command (#859) * Add WTF Python Command * Fix grammar in docstrings, remove redundant variable, remove the use of a wrapper * Fix indentation issues and make use of triple quotes * Update docstrings and remove redundant list() * Change minimum certainty to 75. * Make 'make_embed' function a non async function * Try to unload WTFPython Extension if max fetch requests hit i.e. 3 else try to load the extension. * Correct log messages. * Make flake8 happy :D * Remove redundant class attributes and async functions. * Apply requested grammar and style changes. * Fix unload and load extension logic. * Fix typo in `WTF_PYTHON_RAW_URL` * Changed fuzzy_wuzzy to rapidfuzz Since rapidfuzz also has an extractOne method, this should be a straight replacement with the import statement. * Move wtf_python.py to bot/exts/utilities, flake8 Moved the file to the correct location after merge with main, made changes from the last open suggestions from the previous PR, had to make WTF lowercase to pass flake8 on lines 54 and 118. * Fix trailing commas and long lines * # This is a combination of 3 commits. # This is the 1st commit message: Squashing small commits Small changes and fixes -Added "the" to setup docstring -Fixed typo for mis-matched WTF and wtf in get_wtf_python_readme -Fixed ext location -Added more information to fuzzy_match_header docstring regarding the MINIMUM_CERTAINTY and what the score / value represents. Add wildcard to capture unused return Updated MINIMUM_CERTAINTY to 75 Change MINIMUM_CERTAINTY to 50 Squash commits from Bluenix suggestions Fix docstring for fuzzy_match_header Swap if / else for match Fix functools import Rename get_wtf_python_readme to fetch_readme Collapse self.headers into one line Fix docstring for fuzzy_match_header Swap if / else for match # This is the commit message #2: Fix functools import # This is the commit message #3: Rename get_wtf_python_readme to fetch_readme * Squashing commits Squashing small commits Small changes and fixes -Added "the" to setup docstring -Fixed typo for mis-matched WTF and wtf in get_wtf_python_readme -Fixed ext location -Added more information to fuzzy_match_header docstring regarding the MINIMUM_CERTAINTY and what the score / value represents. Add wildcard to capture unused return Updated MINIMUM_CERTAINTY to 75 Change MINIMUM_CERTAINTY to 50 Squash commits from Bluenix suggestions Fix docstring for fuzzy_match_header Swap if / else for match Fix functools import Rename get_wtf_python_readme to fetch_readme Collapse self.headers into one line Fix docstring for fuzzy_match_header Swap if / else for match Fix functools import Rename get_wtf_python_readme to fetch_readme Collapse self.headers into one line Fix type hints with dict Add match comment for clarity * Add debug logs, and send embed * Add markdown file creation Big change here is to create a .md file based on the matched header. I save the raw text as a class attribute, then slice it based on the index returned by the .find() method for the header, and the separator "/n---/n". * Move the list(map(str.strip , ...) to for loop * Remove line * Use StringIO for file creation * Update file creation with StringIO * Remove embed file preview * chore: update wtf_python docstring * chore: change regex to search, remove file preview * feat: update caching as recommended Minor fixes to import statements as well. Co-authored-by: Bluenix2 * chore: remove logging statements * feat: scheduled task for fetch_readme * chore: fix hyperlink, remove dead code * fix: capitalization clean up * chore: remove unused code * chore: remove more unused code * feat: add light grey logo image in embed * feat: add light grey image * chore: remove debug log message * feat: add found search result header * feat: limit user query to 50 characters * cleanup: remove debug logging * fix: restructure if not match statement Co-authored-by: Bluenix Co-authored-by: Shivansh-007 Co-authored-by: Shivansh-007 Co-authored-by: Bluenix2 Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/utilities/wtf_python.py | 126 ++++++++++++++++++++++++++++ bot/resources/utilities/wtf_python_logo.jpg | Bin 0 -> 19481 bytes 2 files changed, 126 insertions(+) create mode 100644 bot/exts/utilities/wtf_python.py create mode 100644 bot/resources/utilities/wtf_python_logo.jpg (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/wtf_python.py b/bot/exts/utilities/wtf_python.py new file mode 100644 index 00000000..66a022d7 --- /dev/null +++ b/bot/exts/utilities/wtf_python.py @@ -0,0 +1,126 @@ +import logging +import random +import re +from typing import Optional + +import rapidfuzz +from discord import Embed, File +from discord.ext import commands, tasks + +from bot import constants +from bot.bot import Bot + +log = logging.getLogger(__name__) + +WTF_PYTHON_RAW_URL = "http://raw.githubusercontent.com/satwikkansal/wtfpython/master/" +BASE_URL = "https://github.com/satwikkansal/wtfpython" +LOGO_PATH = "./bot/resources/utilities/wtf_python_logo.jpg" + +ERROR_MESSAGE = f""" +Unknown WTF Python Query. Please try to reformulate your query. + +**Examples**: +```md +{constants.Client.prefix}wtf wild imports +{constants.Client.prefix}wtf subclass +{constants.Client.prefix}wtf del +``` +If the problem persists send a message in <#{constants.Channels.dev_contrib}> +""" + +MINIMUM_CERTAINTY = 55 + + +class WTFPython(commands.Cog): + """Cog that allows getting WTF Python entries from the WTF Python repository.""" + + def __init__(self, bot: Bot): + self.bot = bot + self.headers: dict[str, str] = {} + self.fetch_readme.start() + + @tasks.loop(minutes=60) + async def fetch_readme(self) -> None: + """Gets the content of README.md from the WTF Python Repository.""" + async with self.bot.http_session.get(f"{WTF_PYTHON_RAW_URL}README.md") as resp: + log.trace("Fetching the latest WTF Python README.md") + if resp.status == 200: + raw = await resp.text() + self.parse_readme(raw) + + def parse_readme(self, data: str) -> None: + """ + Parses the README.md into a dict. + + It parses the readme into the `self.headers` dict, + where the key is the heading and the value is the + link to the heading. + """ + # Match the start of examples, until the end of the table of contents (toc) + table_of_contents = re.search( + r"\[👀 Examples\]\(#-examples\)\n([\w\W]*)", data + )[0].split("\n") + + for header in list(map(str.strip, table_of_contents)): + match = re.search(r"\[▶ (.*)\]\((.*)\)", header) + if match: + hyper_link = match[0].split("(")[1].replace(")", "") + self.headers[match[0]] = f"{BASE_URL}/{hyper_link}" + + def fuzzy_match_header(self, query: str) -> Optional[str]: + """ + Returns the fuzzy match of a query if its ratio is above "MINIMUM_CERTAINTY" else returns None. + + "MINIMUM_CERTAINTY" is the lowest score at which the fuzzy match will return a result. + The certainty returned by rapidfuzz.process.extractOne is a score between 0 and 100, + with 100 being a perfect match. + """ + match, certainty, _ = rapidfuzz.process.extractOne(query, self.headers.keys()) + return match if certainty > MINIMUM_CERTAINTY else None + + @commands.command(aliases=("wtf", "WTF")) + async def wtf_python(self, ctx: commands.Context, *, query: str) -> None: + """ + Search WTF Python repository. + + Gets the link of the fuzzy matched query from https://github.com/satwikkansal/wtfpython. + Usage: + --> .wtf wild imports + """ + if len(query) > 50: + embed = Embed( + title=random.choice(constants.ERROR_REPLIES), + description=ERROR_MESSAGE, + colour=constants.Colours.soft_red, + ) + match = None + else: + match = self.fuzzy_match_header(query) + + if not match: + embed = Embed( + title=random.choice(constants.ERROR_REPLIES), + description=ERROR_MESSAGE, + colour=constants.Colours.soft_red, + ) + await ctx.send(embed=embed) + return + + embed = Embed( + title="WTF Python?!", + colour=constants.Colours.dark_green, + description=f"""Search result for '{query}': {match.split("]")[0].replace("[", "")} + [Go to Repository Section]({self.headers[match]})""", + ) + logo = File(LOGO_PATH, filename="wtf_logo.jpg") + embed.set_thumbnail(url="attachment://wtf_logo.jpg") + await ctx.send(embed=embed, file=logo) + + def cog_unload(self) -> None: + """Unload the cog and cancel the task.""" + self.fetch_readme.cancel() + + +def setup(bot: Bot) -> None: + """Load the WTFPython Cog.""" + bot.add_cog(WTFPython(bot)) diff --git a/bot/resources/utilities/wtf_python_logo.jpg b/bot/resources/utilities/wtf_python_logo.jpg new file mode 100644 index 00000000..851d7f9a Binary files /dev/null and b/bot/resources/utilities/wtf_python_logo.jpg differ -- cgit v1.2.3 From cdaa77830f9bce1529d93990f00415dbde33a0cd Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Fri, 22 Oct 2021 07:25:39 +0000 Subject: Isort: give the codebase a sort --- bot/__init__.py | 1 - bot/exts/core/help.py | 5 +---- bot/exts/core/internal_eval/_internal_eval.py | 1 + bot/exts/events/advent_of_code/_cog.py | 4 +--- bot/exts/holidays/easter/earth_photos.py | 3 +-- bot/exts/holidays/halloween/scarymovie.py | 1 + bot/exts/utilities/issues.py | 9 +-------- bot/utils/checks.py | 9 +-------- bot/utils/halloween/spookifications.py | 3 +-- 9 files changed, 8 insertions(+), 28 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/__init__.py b/bot/__init__.py index db576cb2..cfaee9f8 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -18,7 +18,6 @@ from discord.ext import commands from bot import monkey_patches from bot.constants import Client - # Configure the "TRACE" logging level (e.g. "log.trace(message)") logging.TRACE = 5 logging.addLevelName(logging.TRACE, "TRACE") diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index 4b766b50..db3c2aa6 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -13,10 +13,7 @@ from rapidfuzz import process from bot import constants from bot.bot import Bot from bot.constants import Emojis -from bot.utils.pagination import ( - FIRST_EMOJI, LAST_EMOJI, - LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, -) +from bot.utils.pagination import FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI DELETE_EMOJI = Emojis.trashcan diff --git a/bot/exts/core/internal_eval/_internal_eval.py b/bot/exts/core/internal_eval/_internal_eval.py index 4f6b4321..12a860fa 100644 --- a/bot/exts/core/internal_eval/_internal_eval.py +++ b/bot/exts/core/internal_eval/_internal_eval.py @@ -10,6 +10,7 @@ from bot.bot import Bot from bot.constants import Client, Roles from bot.utils.decorators import with_role from bot.utils.extensions import invoke_help_command + from ._helpers import EvalContext __all__ = ["InternalEval"] diff --git a/bot/exts/events/advent_of_code/_cog.py b/bot/exts/events/advent_of_code/_cog.py index 7dd967ec..2c1f4541 100644 --- a/bot/exts/events/advent_of_code/_cog.py +++ b/bot/exts/events/advent_of_code/_cog.py @@ -9,9 +9,7 @@ import discord from discord.ext import commands from bot.bot import Bot -from bot.constants import ( - AdventOfCode as AocConfig, Channels, Colours, Emojis, Month, Roles, WHITELISTED_CHANNELS, -) +from bot.constants import AdventOfCode as AocConfig, Channels, Colours, Emojis, Month, Roles, WHITELISTED_CHANNELS from bot.exts.events.advent_of_code import _helpers from bot.exts.events.advent_of_code.views.dayandstarview import AoCDropdownView from bot.utils.decorators import InChannelCheckFailure, in_month, whitelist_override, with_role diff --git a/bot/exts/holidays/easter/earth_photos.py b/bot/exts/holidays/easter/earth_photos.py index f65790af..27442f1c 100644 --- a/bot/exts/holidays/easter/earth_photos.py +++ b/bot/exts/holidays/easter/earth_photos.py @@ -4,8 +4,7 @@ import discord from discord.ext import commands from bot.bot import Bot -from bot.constants import Colours -from bot.constants import Tokens +from bot.constants import Colours, Tokens log = logging.getLogger(__name__) diff --git a/bot/exts/holidays/halloween/scarymovie.py b/bot/exts/holidays/halloween/scarymovie.py index 33659fd8..89310b97 100644 --- a/bot/exts/holidays/halloween/scarymovie.py +++ b/bot/exts/holidays/halloween/scarymovie.py @@ -6,6 +6,7 @@ from discord.ext import commands from bot.bot import Bot from bot.constants import Tokens + log = logging.getLogger(__name__) diff --git a/bot/exts/utilities/issues.py b/bot/exts/utilities/issues.py index 36655e1b..b6d5a43e 100644 --- a/bot/exts/utilities/issues.py +++ b/bot/exts/utilities/issues.py @@ -9,14 +9,7 @@ from discord.ext import commands from bot.bot import Bot from bot.constants import ( - Categories, - Channels, - Colours, - ERROR_REPLIES, - Emojis, - NEGATIVE_REPLIES, - Tokens, - WHITELISTED_CHANNELS + Categories, Channels, Colours, ERROR_REPLIES, Emojis, NEGATIVE_REPLIES, Tokens, WHITELISTED_CHANNELS ) from bot.utils.decorators import whitelist_override from bot.utils.extensions import invoke_help_command diff --git a/bot/utils/checks.py b/bot/utils/checks.py index 612d1ed6..8c426ed7 100644 --- a/bot/utils/checks.py +++ b/bot/utils/checks.py @@ -4,14 +4,7 @@ from collections.abc import Container, Iterable from typing import Callable, Optional from discord.ext.commands import ( - BucketType, - CheckFailure, - Cog, - Command, - CommandOnCooldown, - Context, - Cooldown, - CooldownMapping, + BucketType, CheckFailure, Cog, Command, CommandOnCooldown, Context, Cooldown, CooldownMapping ) from bot import constants diff --git a/bot/utils/halloween/spookifications.py b/bot/utils/halloween/spookifications.py index 93c5ddb9..c45ef8dc 100644 --- a/bot/utils/halloween/spookifications.py +++ b/bot/utils/halloween/spookifications.py @@ -1,8 +1,7 @@ import logging from random import choice, randint -from PIL import Image -from PIL import ImageOps +from PIL import Image, ImageOps log = logging.getLogger() -- cgit v1.2.3 From a054d50b9fdf18c5d1465016f7bd2d3b0abdc0fa Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 24 Oct 2021 18:12:11 -0400 Subject: temp: add restructured template as comments --- bot/exts/utilities/color.py | 137 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 6aa0c3cd..c1523281 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -4,14 +4,16 @@ import logging import re from io import BytesIO -from PIL import Image, ImageColor from discord import Embed, File from discord.ext import commands +from PIL import Image, ImageColor from rapidfuzz import process from bot.bot import Bot from bot.constants import Colours +# from bot.exts.core.extension import invoke_help_command + logger = logging.getLogger(__name__) @@ -28,6 +30,139 @@ with open("bot/resources/utilities/ryanzec_colours.json") as f: COLOR_MAPPING = json.load(f) +THUMBNAIL_SIZE = 80 + +""" +class Colour(commands.Cog): + + def __init__(self, bot: Bot) -> None: + self.bot = bot + + @commands.group(aliases=["color"]) + async def colour(self, ctx: commands.Context) -> None: + if ctx.invoked_subcommand is None: + await invoke_help_command(ctx) + + @colour.command() + async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: + rgb_tuple = ImageColor.getrgb(f"rgb({red}, {green}, {blue})") + await Colour.send_colour_response(ctx, list(rgb_tuple)) + + @colour.command() + async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: + hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") + await Colour.send_colour_response(ctx, list(hsv_tuple)) + + @colour.command() + async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: + hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") + await Colour.send_colour_response(ctx, list(hsl_tuple)) + + @colour.command() + async def cmyk(self, ctx: commands.Context, cyan: int, yellow: int, magenta: int, key: int) -> None: + ... + + @colour.command() + async def hex(self, ctx: commands.Context, hex_code: str) -> None: + hex_tuple = ImageColor.getrgb(hex_code) + await Colour.send_colour_response(ctx, list(hex_tuple)) + + @colour.command() + async def yiq( + self, + ctx: commands.Context, + perceived_luminesence: int, + in_phase: int, + quadrature: int + ) -> None: + yiq_list = list(colorsys.yiq_to_rgb(perceived_luminesence, in_phase, quadrature)) + yiq_tuple = [int(val * 255.0) for val in yiq_list] + await Colour.send_colour_response(ctx, list(yiq_tuple)) + + @staticmethod + async def send_colour_response(ctx: commands.Context, rgb: list[int]) -> Message: + r, g, b = rgb[0], rgb[1], rgb[2] + colour_embed = Embed( + title="Colour", + description="Here lies thy colour", + colour=int(f"{r:02x}{g:02x}{b:02x}", 16) + ) + colour_conversions = Colour.get_colour_conversions(rgb) + for colour_space, value in colour_conversions.items(): + colour_embed.add_field( + name=colour_space.upper(), + value=f"`{value}`", + inline=True + ) + + thumbnail = Image.new("RGB", (THUMBNAIL_SIZE, THUMBNAIL_SIZE), color=tuple(rgb)) + buffer = BytesIO() + thumbnail.save(buffer, "PNG") + buffer.seek(0) + thumbnail_file = File(buffer, filename="colour.png") + + colour_embed.set_thumbnail(url="attachment://colour.png") + + await ctx.send(file=thumbnail_file, embed=colour_embed) + + @staticmethod + def get_colour_conversions(rgb: list[int]) -> dict[str, str]: + return { + "rgb": tuple(rgb), + "hsv": Colour._rgb_to_hsv(rgb), + "hsl": Colour._rgb_to_hsl(rgb), + "cmyk": Colour._rgb_to_cmyk(rgb), + "hex": Colour._rgb_to_hex(rgb), + "yiq": Colour._rgb_to_yiq(rgb) + } + + @staticmethod + def _rgb_to_hsv(rgb: list[int]) -> tuple[int, int, int]: + rgb = [val / 255.0 for val in rgb] + h, v, s = colorsys.rgb_to_hsv(*rgb) + hsv = (round(h * 360), round(s * 100), round(v * 100)) + return hsv + + @staticmethod + def _rgb_to_hsl(rgb: list[int]) -> tuple[int, int, int]: + rgb = [val / 255.0 for val in rgb] + h, l, s = colorsys.rgb_to_hls(*rgb) + hsl = (round(h * 360), round(s * 100), round(l * 100)) + return hsl + + @staticmethod + def _rgb_to_cmyk(rgb: list[int]) -> tuple[int, int, int, int]: + rgb = [val / 255.0 for val in rgb] + + if all(val == 0 for val in rgb): + return 0, 0, 0, 100 + + cmy = [1 - val / 255 for val in rgb] + min_cmy = min(cmy) + + cmyk = [(val - min_cmy) / (1 - min_cmy) for val in cmy] + [min_cmy] + cmyk = [round(val * 100) for val in cmyk] + + return tuple(cmyk) + + @staticmethod + def _rgb_to_hex(rgb: list[int]) -> str: + hex_ = ''.join([hex(val)[2:].zfill(2) for val in rgb]) + hex_code = f"#{hex_}".upper() + return hex_code + + @staticmethod + def _rgb_to_yiq(rgb: list[int]) -> tuple[int, int, int, int]: + rgb = [val / 255.0 for val in rgb] + y, i, q = colorsys.rgb_to_yiq(*rgb) + yiq = (round(y), round(i), round(q)) + return yiq + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Colour(bot)) +""" + + class Color(commands.Cog): """User initiated commands to receive color information.""" -- cgit v1.2.3 From 392616ea9f259ae1bb5f48f2cef366a273436c46 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sun, 24 Oct 2021 18:29:07 -0400 Subject: temp: add restructured layout in comments --- bot/exts/utilities/color.py | 471 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) create mode 100644 bot/exts/utilities/color.py (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py new file mode 100644 index 00000000..c1523281 --- /dev/null +++ b/bot/exts/utilities/color.py @@ -0,0 +1,471 @@ +import colorsys +import json +import logging +import re +from io import BytesIO + +from discord import Embed, File +from discord.ext import commands +from PIL import Image, ImageColor +from rapidfuzz import process + +from bot.bot import Bot +from bot.constants import Colours + +# from bot.exts.core.extension import invoke_help_command + + +logger = logging.getLogger(__name__) + + +ERROR_MSG = """The color code {user_color} is not a possible color combination. +The range of possible values are: +RGB & HSV: 0-255 +CMYK: 0-100% +HSL: 0-360 degrees +Hex: #000000-#FFFFFF +""" + +with open("bot/resources/utilities/ryanzec_colours.json") as f: + COLOR_MAPPING = json.load(f) + + +THUMBNAIL_SIZE = 80 + +""" +class Colour(commands.Cog): + + def __init__(self, bot: Bot) -> None: + self.bot = bot + + @commands.group(aliases=["color"]) + async def colour(self, ctx: commands.Context) -> None: + if ctx.invoked_subcommand is None: + await invoke_help_command(ctx) + + @colour.command() + async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: + rgb_tuple = ImageColor.getrgb(f"rgb({red}, {green}, {blue})") + await Colour.send_colour_response(ctx, list(rgb_tuple)) + + @colour.command() + async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: + hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") + await Colour.send_colour_response(ctx, list(hsv_tuple)) + + @colour.command() + async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: + hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") + await Colour.send_colour_response(ctx, list(hsl_tuple)) + + @colour.command() + async def cmyk(self, ctx: commands.Context, cyan: int, yellow: int, magenta: int, key: int) -> None: + ... + + @colour.command() + async def hex(self, ctx: commands.Context, hex_code: str) -> None: + hex_tuple = ImageColor.getrgb(hex_code) + await Colour.send_colour_response(ctx, list(hex_tuple)) + + @colour.command() + async def yiq( + self, + ctx: commands.Context, + perceived_luminesence: int, + in_phase: int, + quadrature: int + ) -> None: + yiq_list = list(colorsys.yiq_to_rgb(perceived_luminesence, in_phase, quadrature)) + yiq_tuple = [int(val * 255.0) for val in yiq_list] + await Colour.send_colour_response(ctx, list(yiq_tuple)) + + @staticmethod + async def send_colour_response(ctx: commands.Context, rgb: list[int]) -> Message: + r, g, b = rgb[0], rgb[1], rgb[2] + colour_embed = Embed( + title="Colour", + description="Here lies thy colour", + colour=int(f"{r:02x}{g:02x}{b:02x}", 16) + ) + colour_conversions = Colour.get_colour_conversions(rgb) + for colour_space, value in colour_conversions.items(): + colour_embed.add_field( + name=colour_space.upper(), + value=f"`{value}`", + inline=True + ) + + thumbnail = Image.new("RGB", (THUMBNAIL_SIZE, THUMBNAIL_SIZE), color=tuple(rgb)) + buffer = BytesIO() + thumbnail.save(buffer, "PNG") + buffer.seek(0) + thumbnail_file = File(buffer, filename="colour.png") + + colour_embed.set_thumbnail(url="attachment://colour.png") + + await ctx.send(file=thumbnail_file, embed=colour_embed) + + @staticmethod + def get_colour_conversions(rgb: list[int]) -> dict[str, str]: + return { + "rgb": tuple(rgb), + "hsv": Colour._rgb_to_hsv(rgb), + "hsl": Colour._rgb_to_hsl(rgb), + "cmyk": Colour._rgb_to_cmyk(rgb), + "hex": Colour._rgb_to_hex(rgb), + "yiq": Colour._rgb_to_yiq(rgb) + } + + @staticmethod + def _rgb_to_hsv(rgb: list[int]) -> tuple[int, int, int]: + rgb = [val / 255.0 for val in rgb] + h, v, s = colorsys.rgb_to_hsv(*rgb) + hsv = (round(h * 360), round(s * 100), round(v * 100)) + return hsv + + @staticmethod + def _rgb_to_hsl(rgb: list[int]) -> tuple[int, int, int]: + rgb = [val / 255.0 for val in rgb] + h, l, s = colorsys.rgb_to_hls(*rgb) + hsl = (round(h * 360), round(s * 100), round(l * 100)) + return hsl + + @staticmethod + def _rgb_to_cmyk(rgb: list[int]) -> tuple[int, int, int, int]: + rgb = [val / 255.0 for val in rgb] + + if all(val == 0 for val in rgb): + return 0, 0, 0, 100 + + cmy = [1 - val / 255 for val in rgb] + min_cmy = min(cmy) + + cmyk = [(val - min_cmy) / (1 - min_cmy) for val in cmy] + [min_cmy] + cmyk = [round(val * 100) for val in cmyk] + + return tuple(cmyk) + + @staticmethod + def _rgb_to_hex(rgb: list[int]) -> str: + hex_ = ''.join([hex(val)[2:].zfill(2) for val in rgb]) + hex_code = f"#{hex_}".upper() + return hex_code + + @staticmethod + def _rgb_to_yiq(rgb: list[int]) -> tuple[int, int, int, int]: + rgb = [val / 255.0 for val in rgb] + y, i, q = colorsys.rgb_to_yiq(*rgb) + yiq = (round(y), round(i), round(q)) + return yiq + +def setup(bot: commands.Bot) -> None: + bot.add_cog(Colour(bot)) +""" + + +class Color(commands.Cog): + """User initiated commands to receive color information.""" + + def __init__(self, bot: Bot): + self.bot = bot + + @commands.command(aliases=["colour"]) + async def color(self, ctx: commands.Context, mode: str, *, user_color: str) -> None: + """ + Send information on input color code or color name. + + Possible modes are: "hex", "rgb", "hsv", "hsl", "cmyk" or "name". + """ + logger.debug(f"{mode = }") + logger.debug(f"{user_color = }") + if mode.lower() == "hex": + await self.hex_to_rgb(ctx, user_color) + elif mode.lower() == "rgb": + rgb_color = self.tuple_create(user_color) + await self.color_embed(ctx, rgb_color) + elif mode.lower() == "hsv": + await self.hsv_to_rgb(ctx, user_color) + elif mode.lower() == "hsl": + await self.hsl_to_rgb(ctx, user_color) + elif mode.lower() == "cmyk": + await self.cmyk_to_rgb(ctx, user_color) + elif mode.lower() == "name": + color_name, hex_color = self.match_color_name(user_color) + if "#" in hex_color: + rgb_color = ImageColor.getcolor(hex_color, "RGB") + else: + rgb_color = ImageColor.getcolor("#" + hex_color, "RGB") + await self.color_embed(ctx, rgb_color, color_name) + else: + # mode is either None or an invalid code + if mode is None: + no_mode_embed = Embed( + title="No mode was passed, please define a color code.", + description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.", + color=Colours.soft_red, + ) + await ctx.send(embed=no_mode_embed) + return + wrong_mode_embed = Embed( + title=f"The color code {mode} is not a valid option", + description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.", + color=Colours.soft_red, + ) + await ctx.send(embed=wrong_mode_embed) + return + + @staticmethod + def tuple_create(input_color: str) -> tuple[int, int, int]: + """ + Create a tuple of integers based on user's input. + + Can handle inputs of the types: + (100, 100, 100) + 100, 100, 100 + 100 100 100 + """ + if "(" in input_color: + remove = "[() ]" + color_tuple = re.sub(remove, "", input_color) + color_tuple = tuple(map(int, color_tuple.split(","))) + elif "," in input_color: + color_tuple = tuple(map(int, input_color.split(","))) + else: + color_tuple = tuple(map(int, input_color.split(" "))) + return color_tuple + + async def hex_to_rgb(self, ctx: commands.Context, hex_string: str) -> None: + """Function to convert hex color to rgb color and send main embed.""" + hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", hex_string) + if hex_match: + if "#" in hex_string: + rgb_color = ImageColor.getcolor(hex_string, "RGB") + elif "0x" in hex_string: + hex_ = hex_string.replace("0x", "#") + rgb_color = ImageColor.getcolor(hex_, "RGB") + else: + hex_ = "#" + hex_string + rgb_color = ImageColor.getcolor(hex_, "RGB") + await self.color_embed(ctx, rgb_color) + else: + await ctx.send( + embed=Embed( + title="There was an issue converting the hex color code.", + description=ERROR_MSG.format(user_color=hex_string), + ) + ) + + async def hsv_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsv color to rgb color and send main embed.""" + input_color = self.tuple_create(input_color) + (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped + h = h / 360 + s = s / 100 + v = v / 100 + rgb_color = colorsys.hsv_to_rgb(h, s, v) + (r, g, b) = rgb_color + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + await self.color_embed(ctx, (r, g, b)) + + async def hsl_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int]) -> tuple[int, int, int]: + """Function to convert hsl color to rgb color and send main embed.""" + input_color = self.tuple_create(input_color) + (h, s, l) = input_color + h = h / 360 + s = s / 100 + l = l / 100 # noqa: E741 It's little `L`, Reason: To maintain consistency. + rgb_color = colorsys.hls_to_rgb(h, l, s) + (r, g, b) = rgb_color + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + await self.color_embed(ctx, (r, g, b)) + + async def cmyk_to_rgb( + self, + ctx: commands.Context, + input_color: tuple[int, int, int, int] + ) -> tuple[int, int, int]: + """Function to convert cmyk color to rgb color and send main embed.""" + input_color = self.tuple_create(input_color) + c = input_color[0] + m = input_color[1] + y = input_color[2] + k = input_color[3] + r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) + g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) + b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) + await self.color_embed(ctx, (r, g, b)) + + @staticmethod + async def create_thumbnail_attachment(color: tuple[int, int, int]) -> File: + """ + Generate a thumbnail from `color`. + + Assumes that color is an rgb tuple. + """ + thumbnail = Image.new("RGB", (80, 80), color=color) + bufferedio = BytesIO() + thumbnail.save(bufferedio, format="PNG") + bufferedio.seek(0) + + file = File(bufferedio, filename="color.png") + + return file + + @staticmethod + def get_color_fields(rgb_color: tuple[int, int, int]) -> list[dict]: + """Converts from `RGB` to `CMYK`, `HSV`, `HSL` and returns a list of fields.""" + + def _rgb_to_hex(rgb_color: tuple[int, int, int]) -> str: + """To convert from `RGB` to `Hex` notation.""" + return '#' + ''.join(hex(int(color))[2:].zfill(2) for color in rgb_color).upper() + + def _rgb_to_cmyk(rgb_color: tuple[int, int, int]) -> tuple[int, int, int, int]: + """To convert from `RGB` to `CMYK` color space.""" + r, g, b = rgb_color + + # RGB_SCALE -> 255 + # CMYK_SCALE -> 100 + + if (r == g == b == 0): + return 0, 0, 0, 100 # Representing Black + + # rgb [0,RGB_SCALE] -> cmy [0,1] + c = 1 - r / 255 + m = 1 - g / 255 + y = 1 - b / 255 + + # extract out k [0, 1] + min_cmy = min(c, m, y) + c = (c - min_cmy) / (1 - min_cmy) + m = (m - min_cmy) / (1 - min_cmy) + y = (y - min_cmy) / (1 - min_cmy) + k = min_cmy + + # rescale to the range [0,CMYK_SCALE] and round off + c = round(c * 100) + m = round(m * 100) + y = round(y * 100) + k = round(k * 100) + + return c, m, y, k + + def _rgb_to_hsv(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: + """To convert from `RGB` to `HSV` color space.""" + r, g, b = rgb_color + h, v, s = colorsys.rgb_to_hsv(r / float(255), g / float(255), b / float(255)) + h = round(h * 360) + s = round(s * 100) + v = round(v * 100) + return h, s, v + + def _rgb_to_hsl(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: + """To convert from `RGB` to `HSL` color space.""" + r, g, b = rgb_color + h, l, s = colorsys.rgb_to_hls(r / float(255), g / float(255), b / float(255)) + h = round(h * 360) + s = round(s * 100) + l = round(l * 100) # noqa: E741 It's little `L`, Reason: To maintain consistency. + return h, s, l + + all_fields = [ + { + "name": "RGB", + "value": f"» rgb {rgb_color}" + }, + { + "name": "HEX", + "value": f"» hex {_rgb_to_hex(rgb_color)}" + }, + { + "name": "CMYK", + "value": f"» cmyk {_rgb_to_cmyk(rgb_color)}" + }, + { + "name": "HSV", + "value": f"» hsv {_rgb_to_hsv(rgb_color)}" + }, + { + "name": "HSL", + "value": f"» hsl {_rgb_to_hsl(rgb_color)}" + }, + ] + + return all_fields + + @staticmethod + def match_color_name(input_color_name: str) -> str: + """Use fuzzy matching to return a hex color code based on the user's input.""" + try: + match, certainty, _ = process.extractOne( + query=input_color_name, + choices=COLOR_MAPPING.keys(), + score_cutoff=50 + ) + logger.debug(f"{match = }, {certainty = }") + hex_match = COLOR_MAPPING[match] + logger.debug(f"{hex_match = }") + except TypeError: + match = "No color name match found." + hex_match = input_color_name + + return match, hex_match + + @staticmethod + def match_color_hex(input_hex_color: str) -> str: + """Use fuzzy matching to return a hex color code based on the user's input.""" + try: + match, certainty, _ = process.extractOne( + query=input_hex_color, + choices=COLOR_MAPPING.values(), + score_cutoff=80 + ) + logger.debug(f"{match = }, {certainty = }") + color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0] + logger.debug(f"{color_name = }") + except TypeError: + color_name = "No color name match found." + + return color_name + + async def color_embed( + self, + ctx: commands.Context, + rgb_color: tuple[int, int, int], + color_name: str = None + ) -> None: + """Take a RGB color tuple, create embed, and send.""" + (r, g, b) = rgb_color + discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) + all_colors = self.get_color_fields(rgb_color) + hex_color = all_colors[1]["value"].replace("» hex ", "") + if color_name is None: + logger.debug(f"Find color name from hex color: {hex_color}") + color_name = self.match_color_hex(hex_color) + + async with ctx.typing(): + main_embed = Embed( + title=color_name, + description='(Approx..)', + color=discord_rgb_int, + ) + + file = await self.create_thumbnail_attachment(rgb_color) + main_embed.set_thumbnail(url="attachment://color.png") + + for field in all_colors: + main_embed.add_field( + name=field['name'], + value=field['value'], + inline=False, + ) + + await ctx.send(file=file, embed=main_embed) + + +def setup(bot: Bot) -> None: + """Load the Color Cog.""" + bot.add_cog(Color(bot)) -- cgit v1.2.3 From d28b932f4177f5d3056991a3c1872988f69dc952 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Mon, 25 Oct 2021 19:30:34 -0400 Subject: fix: finish restructure with all functionality Added commands for `name` and `random`. Added the ability to look up the color name based on the hex value. Co-authored-by: Mohammad Rafivulla <77384412+CyberCitizen01@users.noreply.github.com> Co-authored-by: Vivaan Verma <54081925+doublevcodes@users.noreply.github.com> --- bot/exts/utilities/color.py | 395 ++++++++------------------------------------ 1 file changed, 66 insertions(+), 329 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index c1523281..dc63cf84 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,7 +1,7 @@ import colorsys import json import logging -import re +import random from io import BytesIO from discord import Embed, File @@ -10,81 +10,97 @@ from PIL import Image, ImageColor from rapidfuzz import process from bot.bot import Bot -from bot.constants import Colours - -# from bot.exts.core.extension import invoke_help_command - +from bot.exts.core.extensions import invoke_help_command logger = logging.getLogger(__name__) -ERROR_MSG = """The color code {user_color} is not a possible color combination. -The range of possible values are: -RGB & HSV: 0-255 -CMYK: 0-100% -HSL: 0-360 degrees -Hex: #000000-#FFFFFF -""" - with open("bot/resources/utilities/ryanzec_colours.json") as f: COLOR_MAPPING = json.load(f) THUMBNAIL_SIZE = 80 -""" + class Colour(commands.Cog): + """Cog for the Colour command.""" def __init__(self, bot: Bot) -> None: self.bot = bot @commands.group(aliases=["color"]) async def colour(self, ctx: commands.Context) -> None: + """ + User initiated command to create an embed that displays color information. + + For the commands `hsl`, `hsv` and `rgb`: input is in the form `.color ` + For the command `cmyk`: input is in the form `.color cmyk ` + For the command `hex`: input is in the form `.color hex #` + For the command `name`: input is in the form `.color name ` + For the command `random`: input is in the form `.color random` + """ if ctx.invoked_subcommand is None: await invoke_help_command(ctx) @colour.command() async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: + """Function to create an embed from an RGB input.""" rgb_tuple = ImageColor.getrgb(f"rgb({red}, {green}, {blue})") await Colour.send_colour_response(ctx, list(rgb_tuple)) @colour.command() async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: + """Function to create an embed from an HSV input.""" hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") await Colour.send_colour_response(ctx, list(hsv_tuple)) @colour.command() async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: + """Function to create an embed from an HSL input.""" hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") await Colour.send_colour_response(ctx, list(hsl_tuple)) @colour.command() async def cmyk(self, ctx: commands.Context, cyan: int, yellow: int, magenta: int, key: int) -> None: - ... + """Function to create an embed from a CMYK input.""" + r = int(255 * (1.0 - cyan / float(100)) * (1.0 - key / float(100))) + g = int(255 * (1.0 - magenta / float(100)) * (1.0 - key / float(100))) + b = int(255 * (1.0 - yellow / float(100)) * (1.0 - key / float(100))) + await Colour.send_colour_response(ctx, list((r, g, b))) @colour.command() async def hex(self, ctx: commands.Context, hex_code: str) -> None: + """Function to create an embed from a HEX input. (Requires # as a prefix).""" hex_tuple = ImageColor.getrgb(hex_code) await Colour.send_colour_response(ctx, list(hex_tuple)) @colour.command() - async def yiq( - self, - ctx: commands.Context, - perceived_luminesence: int, - in_phase: int, - quadrature: int - ) -> None: - yiq_list = list(colorsys.yiq_to_rgb(perceived_luminesence, in_phase, quadrature)) - yiq_tuple = [int(val * 255.0) for val in yiq_list] - await Colour.send_colour_response(ctx, list(yiq_tuple)) + async def name(self, ctx: commands.Context, user_color: str) -> None: + """Function to create an embed from a name input.""" + _, hex_color = self.match_color_name(user_color) + hex_tuple = ImageColor.getrgb(hex_color) + await Colour.send_colour_response(ctx, list(hex_tuple)) + + @colour.command() + async def random(self, ctx: commands.Context) -> None: + """Function to create an embed from a randomly chosen color from the ryanzec.json file.""" + color_choices = list(COLOR_MAPPING.values()) + hex_color = random.choice(color_choices) + hex_tuple = ImageColor.getrgb(f"#{hex_color}") + await Colour.send_colour_response(ctx, list(hex_tuple)) @staticmethod - async def send_colour_response(ctx: commands.Context, rgb: list[int]) -> Message: + async def send_colour_response(ctx: commands.Context, rgb: list[int]) -> None: + """Function to create and send embed from color information.""" r, g, b = rgb[0], rgb[1], rgb[2] + name = Colour._rgb_to_name(rgb) + if name is None: + desc = "Color information for the input color." + else: + desc = f"Color information for {name}" colour_embed = Embed( title="Colour", - description="Here lies thy colour", + description=desc, colour=int(f"{r:02x}{g:02x}{b:02x}", 16) ) colour_conversions = Colour.get_colour_conversions(rgb) @@ -107,17 +123,19 @@ class Colour(commands.Cog): @staticmethod def get_colour_conversions(rgb: list[int]) -> dict[str, str]: + """Create a dictionary mapping of color types and their values.""" return { "rgb": tuple(rgb), "hsv": Colour._rgb_to_hsv(rgb), "hsl": Colour._rgb_to_hsl(rgb), "cmyk": Colour._rgb_to_cmyk(rgb), "hex": Colour._rgb_to_hex(rgb), - "yiq": Colour._rgb_to_yiq(rgb) + "name": Colour._rgb_to_name(rgb) } @staticmethod def _rgb_to_hsv(rgb: list[int]) -> tuple[int, int, int]: + """Function to convert an RGB list to a HSV list.""" rgb = [val / 255.0 for val in rgb] h, v, s = colorsys.rgb_to_hsv(*rgb) hsv = (round(h * 360), round(s * 100), round(v * 100)) @@ -125,6 +143,7 @@ class Colour(commands.Cog): @staticmethod def _rgb_to_hsl(rgb: list[int]) -> tuple[int, int, int]: + """Function to convert an RGB list to a HSL list.""" rgb = [val / 255.0 for val in rgb] h, l, s = colorsys.rgb_to_hls(*rgb) hsl = (round(h * 360), round(s * 100), round(l * 100)) @@ -132,269 +151,39 @@ class Colour(commands.Cog): @staticmethod def _rgb_to_cmyk(rgb: list[int]) -> tuple[int, int, int, int]: + """Function to convert an RGB list to a CMYK list.""" rgb = [val / 255.0 for val in rgb] - if all(val == 0 for val in rgb): return 0, 0, 0, 100 - cmy = [1 - val / 255 for val in rgb] min_cmy = min(cmy) - cmyk = [(val - min_cmy) / (1 - min_cmy) for val in cmy] + [min_cmy] cmyk = [round(val * 100) for val in cmyk] - return tuple(cmyk) @staticmethod def _rgb_to_hex(rgb: list[int]) -> str: + """Function to convert an RGB list to a HEX string.""" hex_ = ''.join([hex(val)[2:].zfill(2) for val in rgb]) hex_code = f"#{hex_}".upper() return hex_code @staticmethod - def _rgb_to_yiq(rgb: list[int]) -> tuple[int, int, int, int]: - rgb = [val / 255.0 for val in rgb] - y, i, q = colorsys.rgb_to_yiq(*rgb) - yiq = (round(y), round(i), round(q)) - return yiq - -def setup(bot: commands.Bot) -> None: - bot.add_cog(Colour(bot)) -""" - - -class Color(commands.Cog): - """User initiated commands to receive color information.""" - - def __init__(self, bot: Bot): - self.bot = bot - - @commands.command(aliases=["colour"]) - async def color(self, ctx: commands.Context, mode: str, *, user_color: str) -> None: - """ - Send information on input color code or color name. - - Possible modes are: "hex", "rgb", "hsv", "hsl", "cmyk" or "name". - """ - logger.debug(f"{mode = }") - logger.debug(f"{user_color = }") - if mode.lower() == "hex": - await self.hex_to_rgb(ctx, user_color) - elif mode.lower() == "rgb": - rgb_color = self.tuple_create(user_color) - await self.color_embed(ctx, rgb_color) - elif mode.lower() == "hsv": - await self.hsv_to_rgb(ctx, user_color) - elif mode.lower() == "hsl": - await self.hsl_to_rgb(ctx, user_color) - elif mode.lower() == "cmyk": - await self.cmyk_to_rgb(ctx, user_color) - elif mode.lower() == "name": - color_name, hex_color = self.match_color_name(user_color) - if "#" in hex_color: - rgb_color = ImageColor.getcolor(hex_color, "RGB") - else: - rgb_color = ImageColor.getcolor("#" + hex_color, "RGB") - await self.color_embed(ctx, rgb_color, color_name) - else: - # mode is either None or an invalid code - if mode is None: - no_mode_embed = Embed( - title="No mode was passed, please define a color code.", - description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.", - color=Colours.soft_red, - ) - await ctx.send(embed=no_mode_embed) - return - wrong_mode_embed = Embed( - title=f"The color code {mode} is not a valid option", - description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.", - color=Colours.soft_red, - ) - await ctx.send(embed=wrong_mode_embed) - return - - @staticmethod - def tuple_create(input_color: str) -> tuple[int, int, int]: - """ - Create a tuple of integers based on user's input. - - Can handle inputs of the types: - (100, 100, 100) - 100, 100, 100 - 100 100 100 - """ - if "(" in input_color: - remove = "[() ]" - color_tuple = re.sub(remove, "", input_color) - color_tuple = tuple(map(int, color_tuple.split(","))) - elif "," in input_color: - color_tuple = tuple(map(int, input_color.split(","))) - else: - color_tuple = tuple(map(int, input_color.split(" "))) - return color_tuple - - async def hex_to_rgb(self, ctx: commands.Context, hex_string: str) -> None: - """Function to convert hex color to rgb color and send main embed.""" - hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", hex_string) - if hex_match: - if "#" in hex_string: - rgb_color = ImageColor.getcolor(hex_string, "RGB") - elif "0x" in hex_string: - hex_ = hex_string.replace("0x", "#") - rgb_color = ImageColor.getcolor(hex_, "RGB") - else: - hex_ = "#" + hex_string - rgb_color = ImageColor.getcolor(hex_, "RGB") - await self.color_embed(ctx, rgb_color) - else: - await ctx.send( - embed=Embed( - title="There was an issue converting the hex color code.", - description=ERROR_MSG.format(user_color=hex_string), - ) + def _rgb_to_name(rgb: list[int]) -> str: + """Function to convert from an RGB list to a fuzzy matched color name.""" + input_hex_color = Colour._rgb_to_hex(rgb) + try: + match, certainty, _ = process.extractOne( + query=input_hex_color, + choices=COLOR_MAPPING.values(), + score_cutoff=80 ) - - async def hsv_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int]) -> tuple[int, int, int]: - """Function to convert hsv color to rgb color and send main embed.""" - input_color = self.tuple_create(input_color) - (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped - h = h / 360 - s = s / 100 - v = v / 100 - rgb_color = colorsys.hsv_to_rgb(h, s, v) - (r, g, b) = rgb_color - r = int(r * 255) - g = int(g * 255) - b = int(b * 255) - await self.color_embed(ctx, (r, g, b)) - - async def hsl_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int]) -> tuple[int, int, int]: - """Function to convert hsl color to rgb color and send main embed.""" - input_color = self.tuple_create(input_color) - (h, s, l) = input_color - h = h / 360 - s = s / 100 - l = l / 100 # noqa: E741 It's little `L`, Reason: To maintain consistency. - rgb_color = colorsys.hls_to_rgb(h, l, s) - (r, g, b) = rgb_color - r = int(r * 255) - g = int(g * 255) - b = int(b * 255) - await self.color_embed(ctx, (r, g, b)) - - async def cmyk_to_rgb( - self, - ctx: commands.Context, - input_color: tuple[int, int, int, int] - ) -> tuple[int, int, int]: - """Function to convert cmyk color to rgb color and send main embed.""" - input_color = self.tuple_create(input_color) - c = input_color[0] - m = input_color[1] - y = input_color[2] - k = input_color[3] - r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100))) - g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100))) - b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100))) - await self.color_embed(ctx, (r, g, b)) - - @staticmethod - async def create_thumbnail_attachment(color: tuple[int, int, int]) -> File: - """ - Generate a thumbnail from `color`. - - Assumes that color is an rgb tuple. - """ - thumbnail = Image.new("RGB", (80, 80), color=color) - bufferedio = BytesIO() - thumbnail.save(bufferedio, format="PNG") - bufferedio.seek(0) - - file = File(bufferedio, filename="color.png") - - return file - - @staticmethod - def get_color_fields(rgb_color: tuple[int, int, int]) -> list[dict]: - """Converts from `RGB` to `CMYK`, `HSV`, `HSL` and returns a list of fields.""" - - def _rgb_to_hex(rgb_color: tuple[int, int, int]) -> str: - """To convert from `RGB` to `Hex` notation.""" - return '#' + ''.join(hex(int(color))[2:].zfill(2) for color in rgb_color).upper() - - def _rgb_to_cmyk(rgb_color: tuple[int, int, int]) -> tuple[int, int, int, int]: - """To convert from `RGB` to `CMYK` color space.""" - r, g, b = rgb_color - - # RGB_SCALE -> 255 - # CMYK_SCALE -> 100 - - if (r == g == b == 0): - return 0, 0, 0, 100 # Representing Black - - # rgb [0,RGB_SCALE] -> cmy [0,1] - c = 1 - r / 255 - m = 1 - g / 255 - y = 1 - b / 255 - - # extract out k [0, 1] - min_cmy = min(c, m, y) - c = (c - min_cmy) / (1 - min_cmy) - m = (m - min_cmy) / (1 - min_cmy) - y = (y - min_cmy) / (1 - min_cmy) - k = min_cmy - - # rescale to the range [0,CMYK_SCALE] and round off - c = round(c * 100) - m = round(m * 100) - y = round(y * 100) - k = round(k * 100) - - return c, m, y, k - - def _rgb_to_hsv(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: - """To convert from `RGB` to `HSV` color space.""" - r, g, b = rgb_color - h, v, s = colorsys.rgb_to_hsv(r / float(255), g / float(255), b / float(255)) - h = round(h * 360) - s = round(s * 100) - v = round(v * 100) - return h, s, v - - def _rgb_to_hsl(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]: - """To convert from `RGB` to `HSL` color space.""" - r, g, b = rgb_color - h, l, s = colorsys.rgb_to_hls(r / float(255), g / float(255), b / float(255)) - h = round(h * 360) - s = round(s * 100) - l = round(l * 100) # noqa: E741 It's little `L`, Reason: To maintain consistency. - return h, s, l - - all_fields = [ - { - "name": "RGB", - "value": f"» rgb {rgb_color}" - }, - { - "name": "HEX", - "value": f"» hex {_rgb_to_hex(rgb_color)}" - }, - { - "name": "CMYK", - "value": f"» cmyk {_rgb_to_cmyk(rgb_color)}" - }, - { - "name": "HSV", - "value": f"» hsv {_rgb_to_hsv(rgb_color)}" - }, - { - "name": "HSL", - "value": f"» hsl {_rgb_to_hsl(rgb_color)}" - }, - ] - - return all_fields + logger.debug(f"{match = }, {certainty = }") + color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0] + logger.debug(f"{color_name = }") + except TypeError: + color_name = None + return color_name @staticmethod def match_color_name(input_color_name: str) -> str: @@ -406,66 +195,14 @@ class Color(commands.Cog): score_cutoff=50 ) logger.debug(f"{match = }, {certainty = }") - hex_match = COLOR_MAPPING[match] + hex_match = f"#{COLOR_MAPPING[match]}" logger.debug(f"{hex_match = }") except TypeError: match = "No color name match found." hex_match = input_color_name - return match, hex_match - @staticmethod - def match_color_hex(input_hex_color: str) -> str: - """Use fuzzy matching to return a hex color code based on the user's input.""" - try: - match, certainty, _ = process.extractOne( - query=input_hex_color, - choices=COLOR_MAPPING.values(), - score_cutoff=80 - ) - logger.debug(f"{match = }, {certainty = }") - color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0] - logger.debug(f"{color_name = }") - except TypeError: - color_name = "No color name match found." - - return color_name - - async def color_embed( - self, - ctx: commands.Context, - rgb_color: tuple[int, int, int], - color_name: str = None - ) -> None: - """Take a RGB color tuple, create embed, and send.""" - (r, g, b) = rgb_color - discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16) - all_colors = self.get_color_fields(rgb_color) - hex_color = all_colors[1]["value"].replace("» hex ", "") - if color_name is None: - logger.debug(f"Find color name from hex color: {hex_color}") - color_name = self.match_color_hex(hex_color) - - async with ctx.typing(): - main_embed = Embed( - title=color_name, - description='(Approx..)', - color=discord_rgb_int, - ) - - file = await self.create_thumbnail_attachment(rgb_color) - main_embed.set_thumbnail(url="attachment://color.png") - - for field in all_colors: - main_embed.add_field( - name=field['name'], - value=field['value'], - inline=False, - ) - - await ctx.send(file=file, embed=main_embed) - def setup(bot: Bot) -> None: - """Load the Color Cog.""" - bot.add_cog(Color(bot)) + """Load the Colour cog.""" + bot.add_cog(Colour(bot)) -- cgit v1.2.3 From d6400337320124ed8ac48445aa2938ac74fb41c5 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Mon, 25 Oct 2021 19:40:55 -0400 Subject: chore: fix import order due to isort --- bot/exts/utilities/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index dc63cf84..b5caf357 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -4,9 +4,9 @@ import logging import random from io import BytesIO +from PIL import Image, ImageColor from discord import Embed, File from discord.ext import commands -from PIL import Image, ImageColor from rapidfuzz import process from bot.bot import Bot -- cgit v1.2.3 From fed6c1315a04ae8970b1a9d1f23f1b8ed4615d86 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Tue, 26 Oct 2021 07:02:37 -0400 Subject: chore: code cleanup --- bot/exts/utilities/color.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index b5caf357..618970df 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -1,6 +1,6 @@ import colorsys import json -import logging +import pathlib import random from io import BytesIO @@ -12,23 +12,16 @@ from rapidfuzz import process from bot.bot import Bot from bot.exts.core.extensions import invoke_help_command -logger = logging.getLogger(__name__) - - -with open("bot/resources/utilities/ryanzec_colours.json") as f: +with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: COLOR_MAPPING = json.load(f) - THUMBNAIL_SIZE = 80 class Colour(commands.Cog): """Cog for the Colour command.""" - def __init__(self, bot: Bot) -> None: - self.bot = bot - - @commands.group(aliases=["color"]) + @commands.group(aliases=("color",)) async def colour(self, ctx: commands.Context) -> None: """ User initiated command to create an embed that displays color information. @@ -97,7 +90,7 @@ class Colour(commands.Cog): if name is None: desc = "Color information for the input color." else: - desc = f"Color information for {name}" + desc = f"Color information for {name}." colour_embed = Embed( title="Colour", description=desc, @@ -178,9 +171,7 @@ class Colour(commands.Cog): choices=COLOR_MAPPING.values(), score_cutoff=80 ) - logger.debug(f"{match = }, {certainty = }") color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0] - logger.debug(f"{color_name = }") except TypeError: color_name = None return color_name @@ -194,9 +185,7 @@ class Colour(commands.Cog): choices=COLOR_MAPPING.keys(), score_cutoff=50 ) - logger.debug(f"{match = }, {certainty = }") hex_match = f"#{COLOR_MAPPING[match]}" - logger.debug(f"{hex_match = }") except TypeError: match = "No color name match found." hex_match = input_color_name -- cgit v1.2.3 From 626df00917a191eb3758a33ec112f4d87bcbb0af Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Tue, 26 Oct 2021 08:15:34 -0400 Subject: chore: code cleanup --- bot/exts/utilities/color.py | 152 +++++++++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 66 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 618970df..606f5fd4 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -12,91 +12,112 @@ from rapidfuzz import process from bot.bot import Bot from bot.exts.core.extensions import invoke_help_command -with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: - COLOR_MAPPING = json.load(f) - -THUMBNAIL_SIZE = 80 +THUMBNAIL_SIZE = (80, 80) class Colour(commands.Cog): """Cog for the Colour command.""" + def __init__(self): + with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: + self.COLOUR_MAPPING = json.load(f) + @commands.group(aliases=("color",)) async def colour(self, ctx: commands.Context) -> None: - """ - User initiated command to create an embed that displays color information. - - For the commands `hsl`, `hsv` and `rgb`: input is in the form `.color ` - For the command `cmyk`: input is in the form `.color cmyk ` - For the command `hex`: input is in the form `.color hex #` - For the command `name`: input is in the form `.color name ` - For the command `random`: input is in the form `.color random` - """ + """User initiated command to create an embed that displays colour information.""" if ctx.invoked_subcommand is None: await invoke_help_command(ctx) @colour.command() async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: - """Function to create an embed from an RGB input.""" + """ + Command to create an embed from an RGB input. + + Input is in the form `.colour rgb ` + """ rgb_tuple = ImageColor.getrgb(f"rgb({red}, {green}, {blue})") - await Colour.send_colour_response(ctx, list(rgb_tuple)) + await self.send_colour_response(ctx, list(rgb_tuple)) @colour.command() async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: - """Function to create an embed from an HSV input.""" + """ + Command to create an embed from an HSV input. + + Input is in the form `.colour hsv ` + """ hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") - await Colour.send_colour_response(ctx, list(hsv_tuple)) + await self.send_colour_response(ctx, list(hsv_tuple)) @colour.command() async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: - """Function to create an embed from an HSL input.""" + """ + Command to create an embed from an HSL input. + + Input is in the form `.colour hsl ` + """ hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") - await Colour.send_colour_response(ctx, list(hsl_tuple)) + await self.send_colour_response(ctx, list(hsl_tuple)) @colour.command() async def cmyk(self, ctx: commands.Context, cyan: int, yellow: int, magenta: int, key: int) -> None: - """Function to create an embed from a CMYK input.""" + """ + Command to create an embed from a CMYK input. + + Input is in the form `.colour cmyk ` + """ r = int(255 * (1.0 - cyan / float(100)) * (1.0 - key / float(100))) g = int(255 * (1.0 - magenta / float(100)) * (1.0 - key / float(100))) b = int(255 * (1.0 - yellow / float(100)) * (1.0 - key / float(100))) - await Colour.send_colour_response(ctx, list((r, g, b))) + await self.send_colour_response(ctx, list((r, g, b))) @colour.command() async def hex(self, ctx: commands.Context, hex_code: str) -> None: - """Function to create an embed from a HEX input. (Requires # as a prefix).""" + """ + Command to create an embed from a HEX input. + + Input is in the form `.colour hex #` + """ hex_tuple = ImageColor.getrgb(hex_code) - await Colour.send_colour_response(ctx, list(hex_tuple)) + await self.send_colour_response(ctx, list(hex_tuple)) @colour.command() - async def name(self, ctx: commands.Context, user_color: str) -> None: - """Function to create an embed from a name input.""" - _, hex_color = self.match_color_name(user_color) - hex_tuple = ImageColor.getrgb(hex_color) - await Colour.send_colour_response(ctx, list(hex_tuple)) + async def name(self, ctx: commands.Context, user_colour: str) -> None: + """ + Command to create an embed from a name input. + + Input is in the form `.colour name ` + """ + _, hex_colour = self.match_colour_name(user_colour) + hex_tuple = ImageColor.getrgb(hex_colour) + await self.send_colour_response(ctx, list(hex_tuple)) @colour.command() async def random(self, ctx: commands.Context) -> None: - """Function to create an embed from a randomly chosen color from the ryanzec.json file.""" - color_choices = list(COLOR_MAPPING.values()) - hex_color = random.choice(color_choices) - hex_tuple = ImageColor.getrgb(f"#{hex_color}") - await Colour.send_colour_response(ctx, list(hex_tuple)) + """ + Command to create an embed from a randomly chosen colour from the reference file. - @staticmethod - async def send_colour_response(ctx: commands.Context, rgb: list[int]) -> None: - """Function to create and send embed from color information.""" + Input is in the form `.colour random` + """ + colour_choices = list(self.COLOUR_MAPPING.values()) + hex_colour = random.choice(colour_choices) + hex_tuple = ImageColor.getrgb(f"#{hex_colour}") + await self.send_colour_response(ctx, list(hex_tuple)) + + async def send_colour_response(self, ctx: commands.Context, rgb: list[int]) -> None: + """Function to create and send embed from colour information.""" r, g, b = rgb[0], rgb[1], rgb[2] - name = Colour._rgb_to_name(rgb) + name = self._rgb_to_name(rgb) + colour_mode = ctx.invoked_command if name is None: - desc = "Color information for the input color." + desc = f"{colour_mode.title()} information for the input colour." else: - desc = f"Color information for {name}." + desc = f"{colour_mode.title()} information for {name}." colour_embed = Embed( title="Colour", description=desc, colour=int(f"{r:02x}{g:02x}{b:02x}", 16) ) - colour_conversions = Colour.get_colour_conversions(rgb) + colour_conversions = self.get_colour_conversions(rgb) for colour_space, value in colour_conversions.items(): colour_embed.add_field( name=colour_space.upper(), @@ -104,7 +125,7 @@ class Colour(commands.Cog): inline=True ) - thumbnail = Image.new("RGB", (THUMBNAIL_SIZE, THUMBNAIL_SIZE), color=tuple(rgb)) + thumbnail = Image.new("RGB", THUMBNAIL_SIZE, color=tuple(rgb)) buffer = BytesIO() thumbnail.save(buffer, "PNG") buffer.seek(0) @@ -114,16 +135,15 @@ class Colour(commands.Cog): await ctx.send(file=thumbnail_file, embed=colour_embed) - @staticmethod - def get_colour_conversions(rgb: list[int]) -> dict[str, str]: - """Create a dictionary mapping of color types and their values.""" + def get_colour_conversions(self, rgb: list[int]) -> dict[str, str]: + """Create a dictionary mapping of colour types and their values.""" return { "rgb": tuple(rgb), - "hsv": Colour._rgb_to_hsv(rgb), - "hsl": Colour._rgb_to_hsl(rgb), - "cmyk": Colour._rgb_to_cmyk(rgb), - "hex": Colour._rgb_to_hex(rgb), - "name": Colour._rgb_to_name(rgb) + "hsv": self._rgb_to_hsv(rgb), + "hsl": self._rgb_to_hsl(rgb), + "cmyk": self._rgb_to_cmyk(rgb), + "hex": self._rgb_to_hex(rgb), + "name": self._rgb_to_name(rgb) } @staticmethod @@ -161,34 +181,34 @@ class Colour(commands.Cog): hex_code = f"#{hex_}".upper() return hex_code - @staticmethod - def _rgb_to_name(rgb: list[int]) -> str: - """Function to convert from an RGB list to a fuzzy matched color name.""" - input_hex_color = Colour._rgb_to_hex(rgb) + @classmethod + def _rgb_to_name(cls, rgb: list[int]) -> str: + """Function to convert from an RGB list to a fuzzy matched colour name.""" + input_hex_colour = cls._rgb_to_hex(rgb) try: match, certainty, _ = process.extractOne( - query=input_hex_color, - choices=COLOR_MAPPING.values(), + query=input_hex_colour, + choices=cls.COLOUR_MAPPING.values(), score_cutoff=80 ) - color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0] + colour_name = [name for name, _ in cls.COLOUR_MAPPING.items() if _ == match][0] except TypeError: - color_name = None - return color_name + colour_name = None + return colour_name - @staticmethod - def match_color_name(input_color_name: str) -> str: - """Use fuzzy matching to return a hex color code based on the user's input.""" + @classmethod + def match_colour_name(cls, input_colour_name: str) -> str: + """Use fuzzy matching to return a hex colour code based on the user's input.""" try: match, certainty, _ = process.extractOne( - query=input_color_name, - choices=COLOR_MAPPING.keys(), + query=input_colour_name, + choices=cls.COLOUR_MAPPING.keys(), score_cutoff=50 ) - hex_match = f"#{COLOR_MAPPING[match]}" + hex_match = f"#{cls.COLOUR_MAPPING[match]}" except TypeError: - match = "No color name match found." - hex_match = input_color_name + match = "No colour name match found." + hex_match = input_colour_name return match, hex_match -- cgit v1.2.3 From d6d8992e68a18819706bcb2a6e40a7ee1e581ca9 Mon Sep 17 00:00:00 2001 From: TizzySaurus <47674925+TizzySaurus@users.noreply.github.com> Date: Wed, 27 Oct 2021 00:13:25 +0100 Subject: Migrate to `og_blurple` (#924) --- bot/exts/core/extensions.py | 2 +- bot/exts/holidays/halloween/candy_collection.py | 2 +- bot/exts/utilities/conversationstarters.py | 2 +- bot/exts/utilities/emoji.py | 2 +- bot/exts/utilities/githubinfo.py | 4 ++-- bot/exts/utilities/reddit.py | 10 +++++----- bot/exts/utilities/wikipedia.py | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/core/extensions.py b/bot/exts/core/extensions.py index dbb9e069..d809d2b9 100644 --- a/bot/exts/core/extensions.py +++ b/bot/exts/core/extensions.py @@ -152,7 +152,7 @@ class Extensions(commands.Cog): Grey indicates that the extension is unloaded. Green indicates that the extension is currently loaded. """ - embed = Embed(colour=Colour.blurple()) + embed = Embed(colour=Colour.og_blurple()) embed.set_author( name="Extensions List", url=Client.github_bot_repo, diff --git a/bot/exts/holidays/halloween/candy_collection.py b/bot/exts/holidays/halloween/candy_collection.py index 09bd0e59..079d900d 100644 --- a/bot/exts/holidays/halloween/candy_collection.py +++ b/bot/exts/holidays/halloween/candy_collection.py @@ -182,7 +182,7 @@ class CandyCollection(commands.Cog): for index, record in enumerate(top_five) ) if top_five else "No Candies" - e = discord.Embed(colour=discord.Colour.blurple()) + e = discord.Embed(colour=discord.Colour.og_blurple()) e.add_field( name="Top Candy Records", value=generate_leaderboard(), diff --git a/bot/exts/utilities/conversationstarters.py b/bot/exts/utilities/conversationstarters.py index dcbfe4d5..8bf2abfd 100644 --- a/bot/exts/utilities/conversationstarters.py +++ b/bot/exts/utilities/conversationstarters.py @@ -53,7 +53,7 @@ class ConvoStarters(commands.Cog): # No matter what, the form will be shown. embed = discord.Embed( description=f"Suggest more topics [here]({SUGGESTION_FORM})!", - color=discord.Color.blurple() + color=discord.Colour.og_blurple() ) try: diff --git a/bot/exts/utilities/emoji.py b/bot/exts/utilities/emoji.py index 83df39cc..fa438d7f 100644 --- a/bot/exts/utilities/emoji.py +++ b/bot/exts/utilities/emoji.py @@ -111,7 +111,7 @@ class Emojis(commands.Cog): **Date:** {datetime.strftime(emoji.created_at.replace(tzinfo=None), "%d/%m/%Y")} **ID:** {emoji.id} """), - color=Color.blurple(), + color=Color.og_blurple(), url=str(emoji.url), ).set_thumbnail(url=emoji.url) diff --git a/bot/exts/utilities/githubinfo.py b/bot/exts/utilities/githubinfo.py index d00b408d..539e388b 100644 --- a/bot/exts/utilities/githubinfo.py +++ b/bot/exts/utilities/githubinfo.py @@ -67,7 +67,7 @@ class GithubInfo(commands.Cog): embed = discord.Embed( title=f"`{user_data['login']}`'s GitHub profile info", description=f"```\n{user_data['bio']}\n```\n" if user_data["bio"] else "", - colour=discord.Colour.blurple(), + colour=discord.Colour.og_blurple(), url=user_data["html_url"], timestamp=datetime.strptime(user_data["created_at"], "%Y-%m-%dT%H:%M:%SZ") ) @@ -139,7 +139,7 @@ class GithubInfo(commands.Cog): embed = discord.Embed( title=repo_data["name"], description=repo_data["description"], - colour=discord.Colour.blurple(), + colour=discord.Colour.og_blurple(), url=repo_data["html_url"] ) diff --git a/bot/exts/utilities/reddit.py b/bot/exts/utilities/reddit.py index e6cb5337..782583d2 100644 --- a/bot/exts/utilities/reddit.py +++ b/bot/exts/utilities/reddit.py @@ -244,7 +244,7 @@ class Reddit(Cog): # Use only starting summary page for #reddit channel posts. embed.description = self.build_pagination_pages(posts, paginate=False) - embed.colour = Colour.blurple() + embed.colour = Colour.og_blurple() return embed @loop() @@ -312,7 +312,7 @@ class Reddit(Cog): await ctx.send(f"Here are the top {subreddit} posts of all time!") embed = Embed( - color=Colour.blurple() + color=Colour.og_blurple() ) await ImagePaginator.paginate(pages, ctx, embed) @@ -325,7 +325,7 @@ class Reddit(Cog): await ctx.send(f"Here are today's top {subreddit} posts!") embed = Embed( - color=Colour.blurple() + color=Colour.og_blurple() ) await ImagePaginator.paginate(pages, ctx, embed) @@ -338,7 +338,7 @@ class Reddit(Cog): await ctx.send(f"Here are this week's top {subreddit} posts!") embed = Embed( - color=Colour.blurple() + color=Colour.og_blurple() ) await ImagePaginator.paginate(pages, ctx, embed) @@ -349,7 +349,7 @@ class Reddit(Cog): """Send a paginated embed of all the subreddits we're relaying.""" embed = Embed() embed.title = "Relayed subreddits." - embed.colour = Colour.blurple() + embed.colour = Colour.og_blurple() await LinePaginator.paginate( RedditConfig.subreddits, diff --git a/bot/exts/utilities/wikipedia.py b/bot/exts/utilities/wikipedia.py index eccc1f8c..c5283de0 100644 --- a/bot/exts/utilities/wikipedia.py +++ b/bot/exts/utilities/wikipedia.py @@ -82,7 +82,7 @@ class WikipediaSearch(commands.Cog): if contents: embed = Embed( title="Wikipedia Search Results", - colour=Color.blurple() + colour=Color.og_blurple() ) embed.set_thumbnail(url=WIKI_THUMBNAIL) embed.timestamp = datetime.utcnow() -- cgit v1.2.3 From 05b56c0d58829b188f55490f9d6825ef95b9d9fb Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Wed, 27 Oct 2021 06:56:35 -0400 Subject: fix: testing fixes --- bot/exts/utilities/color.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 606f5fd4..495a1fd4 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -18,7 +18,8 @@ THUMBNAIL_SIZE = (80, 80) class Colour(commands.Cog): """Cog for the Colour command.""" - def __init__(self): + def __init__(self, bot: Bot): + self.bot = bot with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: self.COLOUR_MAPPING = json.load(f) @@ -107,7 +108,7 @@ class Colour(commands.Cog): """Function to create and send embed from colour information.""" r, g, b = rgb[0], rgb[1], rgb[2] name = self._rgb_to_name(rgb) - colour_mode = ctx.invoked_command + colour_mode = ctx.invoked_with if name is None: desc = f"{colour_mode.title()} information for the input colour." else: @@ -181,31 +182,29 @@ class Colour(commands.Cog): hex_code = f"#{hex_}".upper() return hex_code - @classmethod - def _rgb_to_name(cls, rgb: list[int]) -> str: + def _rgb_to_name(self, rgb: list[int]) -> str: """Function to convert from an RGB list to a fuzzy matched colour name.""" - input_hex_colour = cls._rgb_to_hex(rgb) + input_hex_colour = self._rgb_to_hex(rgb) try: match, certainty, _ = process.extractOne( query=input_hex_colour, - choices=cls.COLOUR_MAPPING.values(), + choices=self.COLOUR_MAPPING.values(), score_cutoff=80 ) - colour_name = [name for name, _ in cls.COLOUR_MAPPING.items() if _ == match][0] + colour_name = [name for name, _ in self.COLOUR_MAPPING.items() if _ == match][0] except TypeError: colour_name = None return colour_name - @classmethod - def match_colour_name(cls, input_colour_name: str) -> str: + def match_colour_name(self, input_colour_name: str) -> str: """Use fuzzy matching to return a hex colour code based on the user's input.""" try: match, certainty, _ = process.extractOne( query=input_colour_name, - choices=cls.COLOUR_MAPPING.keys(), + choices=self.COLOUR_MAPPING.keys(), score_cutoff=50 ) - hex_match = f"#{cls.COLOUR_MAPPING[match]}" + hex_match = f"#{self.COLOUR_MAPPING[match]}" except TypeError: match = "No colour name match found." hex_match = input_colour_name -- cgit v1.2.3 From 0fd2b8f960bcef646a16661f63e0fc21742fb8ab Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 27 Oct 2021 08:01:02 -0400 Subject: chore: embed format tweaking --- bot/exts/utilities/color.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 495a1fd4..a5b374d6 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -110,9 +110,9 @@ class Colour(commands.Cog): name = self._rgb_to_name(rgb) colour_mode = ctx.invoked_with if name is None: - desc = f"{colour_mode.title()} information for the input colour." + desc = f"{colour_mode.upper()} information for the input colour." else: - desc = f"{colour_mode.title()} information for {name}." + desc = f"{colour_mode.upper()} information for `{name}`." colour_embed = Embed( title="Colour", description=desc, -- cgit v1.2.3 From 20a3d55ef27a84e1911ea748e6edbe8b244d3317 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 27 Oct 2021 14:49:42 -0400 Subject: chore: move send_colour_response to top of class --- bot/exts/utilities/color.py | 64 ++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index a5b374d6..ef2421e4 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -23,6 +23,38 @@ class Colour(commands.Cog): with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: self.COLOUR_MAPPING = json.load(f) + async def send_colour_response(self, ctx: commands.Context, rgb: list[int]) -> None: + """Function to create and send embed from colour information.""" + r, g, b = rgb[0], rgb[1], rgb[2] + name = self._rgb_to_name(rgb) + colour_mode = ctx.invoked_with + if name is None: + desc = f"{colour_mode.upper()} information for the input colour." + else: + desc = f"{colour_mode.upper()} information for `{name}`." + colour_embed = Embed( + title="Colour", + description=desc, + colour=int(f"{r:02x}{g:02x}{b:02x}", 16) + ) + colour_conversions = self.get_colour_conversions(rgb) + for colour_space, value in colour_conversions.items(): + colour_embed.add_field( + name=colour_space.upper(), + value=f"`{value}`", + inline=True + ) + + thumbnail = Image.new("RGB", THUMBNAIL_SIZE, color=tuple(rgb)) + buffer = BytesIO() + thumbnail.save(buffer, "PNG") + buffer.seek(0) + thumbnail_file = File(buffer, filename="colour.png") + + colour_embed.set_thumbnail(url="attachment://colour.png") + + await ctx.send(file=thumbnail_file, embed=colour_embed) + @commands.group(aliases=("color",)) async def colour(self, ctx: commands.Context) -> None: """User initiated command to create an embed that displays colour information.""" @@ -104,38 +136,6 @@ class Colour(commands.Cog): hex_tuple = ImageColor.getrgb(f"#{hex_colour}") await self.send_colour_response(ctx, list(hex_tuple)) - async def send_colour_response(self, ctx: commands.Context, rgb: list[int]) -> None: - """Function to create and send embed from colour information.""" - r, g, b = rgb[0], rgb[1], rgb[2] - name = self._rgb_to_name(rgb) - colour_mode = ctx.invoked_with - if name is None: - desc = f"{colour_mode.upper()} information for the input colour." - else: - desc = f"{colour_mode.upper()} information for `{name}`." - colour_embed = Embed( - title="Colour", - description=desc, - colour=int(f"{r:02x}{g:02x}{b:02x}", 16) - ) - colour_conversions = self.get_colour_conversions(rgb) - for colour_space, value in colour_conversions.items(): - colour_embed.add_field( - name=colour_space.upper(), - value=f"`{value}`", - inline=True - ) - - thumbnail = Image.new("RGB", THUMBNAIL_SIZE, color=tuple(rgb)) - buffer = BytesIO() - thumbnail.save(buffer, "PNG") - buffer.seek(0) - thumbnail_file = File(buffer, filename="colour.png") - - colour_embed.set_thumbnail(url="attachment://colour.png") - - await ctx.send(file=thumbnail_file, embed=colour_embed) - def get_colour_conversions(self, rgb: list[int]) -> dict[str, str]: """Create a dictionary mapping of colour types and their values.""" return { -- cgit v1.2.3 From 7d70bb40c164d176e3cb5653335c13196515f854 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 28 Oct 2021 07:09:01 -0400 Subject: chore: general code fixes and cleanup --- bot/exts/utilities/color.py | 91 +++++++++++++++------------------------------ 1 file changed, 29 insertions(+), 62 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index ef2421e4..66dc78de 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -24,14 +24,11 @@ class Colour(commands.Cog): self.COLOUR_MAPPING = json.load(f) async def send_colour_response(self, ctx: commands.Context, rgb: list[int]) -> None: - """Function to create and send embed from colour information.""" + """Create and send embed from user given colour information.""" r, g, b = rgb[0], rgb[1], rgb[2] name = self._rgb_to_name(rgb) colour_mode = ctx.invoked_with - if name is None: - desc = f"{colour_mode.upper()} information for the input colour." - else: - desc = f"{colour_mode.upper()} information for `{name}`." + desc = f"{colour_mode.upper()} information for `{name or 'the input colour'}`." colour_embed = Embed( title="Colour", description=desc, @@ -40,7 +37,7 @@ class Colour(commands.Cog): colour_conversions = self.get_colour_conversions(rgb) for colour_space, value in colour_conversions.items(): colour_embed.add_field( - name=colour_space.upper(), + name=colour_space, value=f"`{value}`", inline=True ) @@ -63,41 +60,25 @@ class Colour(commands.Cog): @colour.command() async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: - """ - Command to create an embed from an RGB input. - - Input is in the form `.colour rgb ` - """ + """Command to create an embed from an RGB input.""" rgb_tuple = ImageColor.getrgb(f"rgb({red}, {green}, {blue})") await self.send_colour_response(ctx, list(rgb_tuple)) @colour.command() async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: - """ - Command to create an embed from an HSV input. - - Input is in the form `.colour hsv ` - """ + """Command to create an embed from an HSV input.""" hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") await self.send_colour_response(ctx, list(hsv_tuple)) @colour.command() async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: - """ - Command to create an embed from an HSL input. - - Input is in the form `.colour hsl ` - """ + """Command to create an embed from an HSL input.""" hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") await self.send_colour_response(ctx, list(hsl_tuple)) @colour.command() async def cmyk(self, ctx: commands.Context, cyan: int, yellow: int, magenta: int, key: int) -> None: - """ - Command to create an embed from a CMYK input. - - Input is in the form `.colour cmyk ` - """ + """Command to create an embed from a CMYK input.""" r = int(255 * (1.0 - cyan / float(100)) * (1.0 - key / float(100))) g = int(255 * (1.0 - magenta / float(100)) * (1.0 - key / float(100))) b = int(255 * (1.0 - yellow / float(100)) * (1.0 - key / float(100))) @@ -105,51 +86,38 @@ class Colour(commands.Cog): @colour.command() async def hex(self, ctx: commands.Context, hex_code: str) -> None: - """ - Command to create an embed from a HEX input. - - Input is in the form `.colour hex #` - """ + """Command to create an embed from a HEX input.""" hex_tuple = ImageColor.getrgb(hex_code) await self.send_colour_response(ctx, list(hex_tuple)) @colour.command() async def name(self, ctx: commands.Context, user_colour: str) -> None: - """ - Command to create an embed from a name input. - - Input is in the form `.colour name ` - """ - _, hex_colour = self.match_colour_name(user_colour) + """Command to create an embed from a name input.""" + hex_colour = self.match_colour_name(user_colour) hex_tuple = ImageColor.getrgb(hex_colour) await self.send_colour_response(ctx, list(hex_tuple)) @colour.command() async def random(self, ctx: commands.Context) -> None: - """ - Command to create an embed from a randomly chosen colour from the reference file. - - Input is in the form `.colour random` - """ - colour_choices = list(self.COLOUR_MAPPING.values()) - hex_colour = random.choice(colour_choices) + """Command to create an embed from a randomly chosen colour from the reference file.""" + hex_colour = random.choice(list(self.COLOUR_MAPPING.values())) hex_tuple = ImageColor.getrgb(f"#{hex_colour}") await self.send_colour_response(ctx, list(hex_tuple)) def get_colour_conversions(self, rgb: list[int]) -> dict[str, str]: """Create a dictionary mapping of colour types and their values.""" return { - "rgb": tuple(rgb), - "hsv": self._rgb_to_hsv(rgb), - "hsl": self._rgb_to_hsl(rgb), - "cmyk": self._rgb_to_cmyk(rgb), - "hex": self._rgb_to_hex(rgb), - "name": self._rgb_to_name(rgb) + "RGB": tuple(rgb), + "HSV": self._rgb_to_hsv(rgb), + "HSL": self._rgb_to_hsl(rgb), + "CMYK": self._rgb_to_cmyk(rgb), + "Hex": self._rgb_to_hex(rgb), + "Name": self._rgb_to_name(rgb) } @staticmethod def _rgb_to_hsv(rgb: list[int]) -> tuple[int, int, int]: - """Function to convert an RGB list to a HSV list.""" + """Convert RGB values to HSV values.""" rgb = [val / 255.0 for val in rgb] h, v, s = colorsys.rgb_to_hsv(*rgb) hsv = (round(h * 360), round(s * 100), round(v * 100)) @@ -157,7 +125,7 @@ class Colour(commands.Cog): @staticmethod def _rgb_to_hsl(rgb: list[int]) -> tuple[int, int, int]: - """Function to convert an RGB list to a HSL list.""" + """Convert RGB values to HSL values.""" rgb = [val / 255.0 for val in rgb] h, l, s = colorsys.rgb_to_hls(*rgb) hsl = (round(h * 360), round(s * 100), round(l * 100)) @@ -165,7 +133,7 @@ class Colour(commands.Cog): @staticmethod def _rgb_to_cmyk(rgb: list[int]) -> tuple[int, int, int, int]: - """Function to convert an RGB list to a CMYK list.""" + """Convert RGB values to CMYK values.""" rgb = [val / 255.0 for val in rgb] if all(val == 0 for val in rgb): return 0, 0, 0, 100 @@ -177,13 +145,13 @@ class Colour(commands.Cog): @staticmethod def _rgb_to_hex(rgb: list[int]) -> str: - """Function to convert an RGB list to a HEX string.""" + """Convert RGB values to HEX code.""" hex_ = ''.join([hex(val)[2:].zfill(2) for val in rgb]) hex_code = f"#{hex_}".upper() return hex_code def _rgb_to_name(self, rgb: list[int]) -> str: - """Function to convert from an RGB list to a fuzzy matched colour name.""" + """Convert RGB values to a fuzzy matched name.""" input_hex_colour = self._rgb_to_hex(rgb) try: match, certainty, _ = process.extractOne( @@ -191,24 +159,23 @@ class Colour(commands.Cog): choices=self.COLOUR_MAPPING.values(), score_cutoff=80 ) - colour_name = [name for name, _ in self.COLOUR_MAPPING.items() if _ == match][0] + colour_name = [name for name, hex_code in self.COLOUR_MAPPING.items() if hex_code == match][0] except TypeError: colour_name = None return colour_name def match_colour_name(self, input_colour_name: str) -> str: - """Use fuzzy matching to return a hex colour code based on the user's input.""" + """Convert a colour name to HEX code.""" try: match, certainty, _ = process.extractOne( query=input_colour_name, choices=self.COLOUR_MAPPING.keys(), - score_cutoff=50 + score_cutoff=90 ) hex_match = f"#{self.COLOUR_MAPPING[match]}" - except TypeError: - match = "No colour name match found." - hex_match = input_colour_name - return match, hex_match + except (ValueError, TypeError): + hex_match = None + return hex_match def setup(bot: Bot) -> None: -- cgit v1.2.3 From a742fe985eaee3757e565b6f8880c014eda15e10 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Thu, 28 Oct 2021 08:49:57 -0400 Subject: chore: change to tuples, various suggested changes --- bot/exts/utilities/color.py | 60 ++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 66dc78de..e9e8dcf6 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -23,9 +23,9 @@ class Colour(commands.Cog): with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: self.COLOUR_MAPPING = json.load(f) - async def send_colour_response(self, ctx: commands.Context, rgb: list[int]) -> None: + async def send_colour_response(self, ctx: commands.Context, rgb: tuple[int, int, int]) -> None: """Create and send embed from user given colour information.""" - r, g, b = rgb[0], rgb[1], rgb[2] + r, g, b = rgb name = self._rgb_to_name(rgb) colour_mode = ctx.invoked_with desc = f"{colour_mode.upper()} information for `{name or 'the input colour'}`." @@ -42,7 +42,7 @@ class Colour(commands.Cog): inline=True ) - thumbnail = Image.new("RGB", THUMBNAIL_SIZE, color=tuple(rgb)) + thumbnail = Image.new("RGB", THUMBNAIL_SIZE, color=rgb) buffer = BytesIO() thumbnail.save(buffer, "PNG") buffer.seek(0) @@ -61,53 +61,53 @@ class Colour(commands.Cog): @colour.command() async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: """Command to create an embed from an RGB input.""" - rgb_tuple = ImageColor.getrgb(f"rgb({red}, {green}, {blue})") - await self.send_colour_response(ctx, list(rgb_tuple)) + rgb_tuple = (red, green, blue) + await self.send_colour_response(ctx, rgb_tuple) @colour.command() async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: """Command to create an embed from an HSV input.""" hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") - await self.send_colour_response(ctx, list(hsv_tuple)) + await self.send_colour_response(ctx, hsv_tuple) @colour.command() async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: """Command to create an embed from an HSL input.""" hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") - await self.send_colour_response(ctx, list(hsl_tuple)) + await self.send_colour_response(ctx, hsl_tuple) @colour.command() async def cmyk(self, ctx: commands.Context, cyan: int, yellow: int, magenta: int, key: int) -> None: """Command to create an embed from a CMYK input.""" - r = int(255 * (1.0 - cyan / float(100)) * (1.0 - key / float(100))) - g = int(255 * (1.0 - magenta / float(100)) * (1.0 - key / float(100))) - b = int(255 * (1.0 - yellow / float(100)) * (1.0 - key / float(100))) - await self.send_colour_response(ctx, list((r, g, b))) + r = int(255 * (1.0 - cyan / 100) * (1.0 - key / 100)) + g = int(255 * (1.0 - magenta / 100) * (1.0 - key / 100)) + b = int(255 * (1.0 - yellow / 100) * (1.0 - key / 100)) + await self.send_colour_response(ctx, (r, g, b)) @colour.command() async def hex(self, ctx: commands.Context, hex_code: str) -> None: """Command to create an embed from a HEX input.""" hex_tuple = ImageColor.getrgb(hex_code) - await self.send_colour_response(ctx, list(hex_tuple)) + await self.send_colour_response(ctx, hex_tuple) @colour.command() - async def name(self, ctx: commands.Context, user_colour: str) -> None: + async def name(self, ctx: commands.Context, user_colour_name: str) -> None: """Command to create an embed from a name input.""" - hex_colour = self.match_colour_name(user_colour) + hex_colour = self.match_colour_name(user_colour_name) hex_tuple = ImageColor.getrgb(hex_colour) - await self.send_colour_response(ctx, list(hex_tuple)) + await self.send_colour_response(ctx, hex_tuple) @colour.command() async def random(self, ctx: commands.Context) -> None: """Command to create an embed from a randomly chosen colour from the reference file.""" hex_colour = random.choice(list(self.COLOUR_MAPPING.values())) hex_tuple = ImageColor.getrgb(f"#{hex_colour}") - await self.send_colour_response(ctx, list(hex_tuple)) + await self.send_colour_response(ctx, hex_tuple) - def get_colour_conversions(self, rgb: list[int]) -> dict[str, str]: + def get_colour_conversions(self, rgb: tuple[int, int, int]) -> dict[str, str]: """Create a dictionary mapping of colour types and their values.""" return { - "RGB": tuple(rgb), + "RGB": rgb, "HSV": self._rgb_to_hsv(rgb), "HSL": self._rgb_to_hsl(rgb), "CMYK": self._rgb_to_cmyk(rgb), @@ -116,41 +116,41 @@ class Colour(commands.Cog): } @staticmethod - def _rgb_to_hsv(rgb: list[int]) -> tuple[int, int, int]: + def _rgb_to_hsv(rgb: tuple[int, int, int]) -> tuple[int, int, int]: """Convert RGB values to HSV values.""" - rgb = [val / 255.0 for val in rgb] - h, v, s = colorsys.rgb_to_hsv(*rgb) + rgb_list = [val / 255.0 for val in rgb] + h, v, s = colorsys.rgb_to_hsv(*rgb_list) hsv = (round(h * 360), round(s * 100), round(v * 100)) return hsv @staticmethod - def _rgb_to_hsl(rgb: list[int]) -> tuple[int, int, int]: + def _rgb_to_hsl(rgb: tuple[int, int, int]) -> tuple[int, int, int]: """Convert RGB values to HSL values.""" - rgb = [val / 255.0 for val in rgb] - h, l, s = colorsys.rgb_to_hls(*rgb) + rgb_list = [val / 255.0 for val in rgb] + h, l, s = colorsys.rgb_to_hls(*rgb_list) hsl = (round(h * 360), round(s * 100), round(l * 100)) return hsl @staticmethod - def _rgb_to_cmyk(rgb: list[int]) -> tuple[int, int, int, int]: + def _rgb_to_cmyk(rgb: tuple[int, int, int, int]) -> tuple[int, int, int, int]: """Convert RGB values to CMYK values.""" - rgb = [val / 255.0 for val in rgb] - if all(val == 0 for val in rgb): + rgb_list = [val / 255.0 for val in rgb] + if not any(rgb_list): return 0, 0, 0, 100 - cmy = [1 - val / 255 for val in rgb] + cmy = [1 - val / 255 for val in rgb_list] min_cmy = min(cmy) cmyk = [(val - min_cmy) / (1 - min_cmy) for val in cmy] + [min_cmy] cmyk = [round(val * 100) for val in cmyk] return tuple(cmyk) @staticmethod - def _rgb_to_hex(rgb: list[int]) -> str: + def _rgb_to_hex(rgb: tuple[int, int, int]) -> str: """Convert RGB values to HEX code.""" hex_ = ''.join([hex(val)[2:].zfill(2) for val in rgb]) hex_code = f"#{hex_}".upper() return hex_code - def _rgb_to_name(self, rgb: list[int]) -> str: + def _rgb_to_name(self, rgb: tuple[int, int, int]) -> str: """Convert RGB values to a fuzzy matched name.""" input_hex_colour = self._rgb_to_hex(rgb) try: -- cgit v1.2.3 From b2151ebc86a538b6e8eb492a2b10a7cd2c12f900 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Thu, 28 Oct 2021 09:01:57 -0400 Subject: chore: correct capitalization of command name in embed --- bot/exts/utilities/color.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index e9e8dcf6..573039fa 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -28,9 +28,14 @@ class Colour(commands.Cog): r, g, b = rgb name = self._rgb_to_name(rgb) colour_mode = ctx.invoked_with - desc = f"{colour_mode.upper()} information for `{name or 'the input colour'}`." + colour_or_color = ctx.command.name + if colour_mode not in ("name", "hex", "random"): + colour_mode = colour_mode.upper() + else: + colour_mode = colour_mode.title() + desc = f"{colour_mode} information for `{name or 'the input colour'}`." colour_embed = Embed( - title="Colour", + title=colour_or_color.title(), description=desc, colour=int(f"{r:02x}{g:02x}{b:02x}", 16) ) -- cgit v1.2.3 From 364a7083ab38c3055c6db514e5203d68d61eb597 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Thu, 28 Oct 2021 09:13:49 -0400 Subject: chore: change COLOUR_MAPPING to colour_mapping --- bot/exts/utilities/color.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 573039fa..9356577f 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -21,7 +21,7 @@ class Colour(commands.Cog): def __init__(self, bot: Bot): self.bot = bot with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: - self.COLOUR_MAPPING = json.load(f) + self.colour_mapping = json.load(f) async def send_colour_response(self, ctx: commands.Context, rgb: tuple[int, int, int]) -> None: """Create and send embed from user given colour information.""" @@ -105,7 +105,7 @@ class Colour(commands.Cog): @colour.command() async def random(self, ctx: commands.Context) -> None: """Command to create an embed from a randomly chosen colour from the reference file.""" - hex_colour = random.choice(list(self.COLOUR_MAPPING.values())) + hex_colour = random.choice(list(self.colour_mapping.values())) hex_tuple = ImageColor.getrgb(f"#{hex_colour}") await self.send_colour_response(ctx, hex_tuple) @@ -161,10 +161,10 @@ class Colour(commands.Cog): try: match, certainty, _ = process.extractOne( query=input_hex_colour, - choices=self.COLOUR_MAPPING.values(), + choices=self.colour_mapping.values(), score_cutoff=80 ) - colour_name = [name for name, hex_code in self.COLOUR_MAPPING.items() if hex_code == match][0] + colour_name = [name for name, hex_code in self.colour_mapping.items() if hex_code == match][0] except TypeError: colour_name = None return colour_name @@ -174,10 +174,10 @@ class Colour(commands.Cog): try: match, certainty, _ = process.extractOne( query=input_colour_name, - choices=self.COLOUR_MAPPING.keys(), + choices=self.colour_mapping.keys(), score_cutoff=90 ) - hex_match = f"#{self.COLOUR_MAPPING[match]}" + hex_match = f"#{self.colour_mapping[match]}" except (ValueError, TypeError): hex_match = None return hex_match -- cgit v1.2.3 From 869f21a7cc376ef2382bc88980b660226b621cdd Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 28 Oct 2021 19:00:31 -0400 Subject: fix: added error handling and other cleanup chores --- bot/exts/utilities/color.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 9356577f..fe57d388 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -7,6 +7,7 @@ from io import BytesIO from PIL import Image, ImageColor from discord import Embed, File from discord.ext import commands +from discord.ext.commands.errors import BadArgument from rapidfuzz import process from bot.bot import Bot @@ -28,12 +29,16 @@ class Colour(commands.Cog): r, g, b = rgb name = self._rgb_to_name(rgb) colour_mode = ctx.invoked_with - colour_or_color = ctx.command.name + try: + colour_or_color = ctx.invoked_parents[0] + except IndexError: + colour_or_color = "colour" + input_colour = ctx.args[2:] if colour_mode not in ("name", "hex", "random"): colour_mode = colour_mode.upper() else: colour_mode = colour_mode.title() - desc = f"{colour_mode} information for `{name or 'the input colour'}`." + desc = f"{colour_mode} information for `{name or input_colour}`." colour_embed = Embed( title=colour_or_color.title(), description=desc, @@ -66,32 +71,50 @@ class Colour(commands.Cog): @colour.command() async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: """Command to create an embed from an RGB input.""" + if (red or green or blue) > 250 or (red or green or blue) < 0: + raise BadArgument( + message=f"RGB values can only be from 0 to 250. User input was: `{red, green, blue}`" + ) rgb_tuple = (red, green, blue) await self.send_colour_response(ctx, rgb_tuple) @colour.command() async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: """Command to create an embed from an HSV input.""" + if (hue or saturation or value) > 250 or (hue or saturation or value) < 0: + raise BadArgument( + message=f"HSV values can only be from 0 to 250. User input was: `{hue, saturation, value}`" + ) hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") await self.send_colour_response(ctx, hsv_tuple) @colour.command() async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: """Command to create an embed from an HSL input.""" + if (hue or saturation or lightness) > 360 or (hue or saturation or lightness) < 0: + raise BadArgument( + message=f"HSL values can only be from 0 to 360. User input was: `{hue, saturation, lightness}`" + ) hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") await self.send_colour_response(ctx, hsl_tuple) @colour.command() async def cmyk(self, ctx: commands.Context, cyan: int, yellow: int, magenta: int, key: int) -> None: """Command to create an embed from a CMYK input.""" - r = int(255 * (1.0 - cyan / 100) * (1.0 - key / 100)) - g = int(255 * (1.0 - magenta / 100) * (1.0 - key / 100)) - b = int(255 * (1.0 - yellow / 100) * (1.0 - key / 100)) + if (cyan or magenta or yellow or key) > 100 or (cyan or magenta or yellow or key) < 0: + raise BadArgument( + message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`" + ) + r = int(255 * (1.0 - cyan / float(100)) * (1.0 - key / float(100))) + g = int(255 * (1.0 - magenta / float(100)) * (1.0 - key / float(100))) + b = int(255 * (1.0 - yellow / float(100)) * (1.0 - key / float(100))) await self.send_colour_response(ctx, (r, g, b)) @colour.command() async def hex(self, ctx: commands.Context, hex_code: str) -> None: """Command to create an embed from a HEX input.""" + if "#" not in hex_code: + hex_code = f"#{hex_code}" hex_tuple = ImageColor.getrgb(hex_code) await self.send_colour_response(ctx, hex_tuple) @@ -175,11 +198,12 @@ class Colour(commands.Cog): match, certainty, _ = process.extractOne( query=input_colour_name, choices=self.colour_mapping.keys(), - score_cutoff=90 + score_cutoff=80 ) hex_match = f"#{self.colour_mapping[match]}" except (ValueError, TypeError): hex_match = None + raise BadArgument(message=f"No color found for: `{input_colour_name}`") return hex_match -- cgit v1.2.3 From 5437712fe6fb5bc2149207f342afdfd37edece6e Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 28 Oct 2021 19:01:11 -0400 Subject: fix: add periods to error messages --- bot/exts/utilities/color.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index fe57d388..1ad6df27 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -73,7 +73,7 @@ class Colour(commands.Cog): """Command to create an embed from an RGB input.""" if (red or green or blue) > 250 or (red or green or blue) < 0: raise BadArgument( - message=f"RGB values can only be from 0 to 250. User input was: `{red, green, blue}`" + message=f"RGB values can only be from 0 to 250. User input was: `{red, green, blue}`." ) rgb_tuple = (red, green, blue) await self.send_colour_response(ctx, rgb_tuple) @@ -83,7 +83,7 @@ class Colour(commands.Cog): """Command to create an embed from an HSV input.""" if (hue or saturation or value) > 250 or (hue or saturation or value) < 0: raise BadArgument( - message=f"HSV values can only be from 0 to 250. User input was: `{hue, saturation, value}`" + message=f"HSV values can only be from 0 to 250. User input was: `{hue, saturation, value}`." ) hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") await self.send_colour_response(ctx, hsv_tuple) @@ -93,7 +93,7 @@ class Colour(commands.Cog): """Command to create an embed from an HSL input.""" if (hue or saturation or lightness) > 360 or (hue or saturation or lightness) < 0: raise BadArgument( - message=f"HSL values can only be from 0 to 360. User input was: `{hue, saturation, lightness}`" + message=f"HSL values can only be from 0 to 360. User input was: `{hue, saturation, lightness}`." ) hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") await self.send_colour_response(ctx, hsl_tuple) @@ -103,7 +103,7 @@ class Colour(commands.Cog): """Command to create an embed from a CMYK input.""" if (cyan or magenta or yellow or key) > 100 or (cyan or magenta or yellow or key) < 0: raise BadArgument( - message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`" + message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`." ) r = int(255 * (1.0 - cyan / float(100)) * (1.0 - key / float(100))) g = int(255 * (1.0 - magenta / float(100)) * (1.0 - key / float(100))) -- cgit v1.2.3 From 1d1dff113de6c276d614a945789755d3c4f9c3ab Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 28 Oct 2021 19:40:09 -0400 Subject: fix: bug in cmyk and hsv --- bot/exts/utilities/color.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 1ad6df27..f594a0df 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -85,7 +85,8 @@ class Colour(commands.Cog): raise BadArgument( message=f"HSV values can only be from 0 to 250. User input was: `{hue, saturation, value}`." ) - hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") + # ImageColor HSV expects S and V to be swapped + hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {value}%, {saturation}%)") await self.send_colour_response(ctx, hsv_tuple) @colour.command() @@ -99,15 +100,15 @@ class Colour(commands.Cog): await self.send_colour_response(ctx, hsl_tuple) @colour.command() - async def cmyk(self, ctx: commands.Context, cyan: int, yellow: int, magenta: int, key: int) -> None: + async def cmyk(self, ctx: commands.Context, cyan: int, magenta: int, yellow: int, key: int) -> None: """Command to create an embed from a CMYK input.""" if (cyan or magenta or yellow or key) > 100 or (cyan or magenta or yellow or key) < 0: raise BadArgument( message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`." ) - r = int(255 * (1.0 - cyan / float(100)) * (1.0 - key / float(100))) - g = int(255 * (1.0 - magenta / float(100)) * (1.0 - key / float(100))) - b = int(255 * (1.0 - yellow / float(100)) * (1.0 - key / float(100))) + r = round(255 * (1 - (cyan / 100)) * (1 - (key / 100))) + g = round(255 * (1 - (magenta / 100)) * (1 - (key / 100))) + b = round(255 * (1 - (yellow / 100)) * (1 - (key / 100))) await self.send_colour_response(ctx, (r, g, b)) @colour.command() @@ -165,11 +166,12 @@ class Colour(commands.Cog): rgb_list = [val / 255.0 for val in rgb] if not any(rgb_list): return 0, 0, 0, 100 - cmy = [1 - val / 255 for val in rgb_list] - min_cmy = min(cmy) - cmyk = [(val - min_cmy) / (1 - min_cmy) for val in cmy] + [min_cmy] - cmyk = [round(val * 100) for val in cmyk] - return tuple(cmyk) + k = 1 - max(val for val in rgb_list) + c = round((1 - rgb_list[0] - k) * 100 / (1 - k)) + m = round((1 - rgb_list[1] - k) * 100 / (1 - k)) + y = round((1 - rgb_list[2] - k) * 100 / (1 - k)) + cmyk = (c, m, y, round(k * 100)) + return cmyk @staticmethod def _rgb_to_hex(rgb: tuple[int, int, int]) -> str: -- cgit v1.2.3 From 3ba2dfdfe51b543ac427b1dd50a5b3dcd73e8afc Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Fri, 29 Oct 2021 06:37:56 -0400 Subject: fix: correct ranges and logic for color error handling --- bot/exts/utilities/color.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index f594a0df..587d4e18 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -33,7 +33,7 @@ class Colour(commands.Cog): colour_or_color = ctx.invoked_parents[0] except IndexError: colour_or_color = "colour" - input_colour = ctx.args[2:] + input_colour = ctx.args[2:][0] if colour_mode not in ("name", "hex", "random"): colour_mode = colour_mode.upper() else: @@ -71,9 +71,9 @@ class Colour(commands.Cog): @colour.command() async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: """Command to create an embed from an RGB input.""" - if (red or green or blue) > 250 or (red or green or blue) < 0: + if any(c not in range(0, 256) for c in (red, green, blue)): raise BadArgument( - message=f"RGB values can only be from 0 to 250. User input was: `{red, green, blue}`." + message=f"RGB values can only be from 0 to 255. User input was: `{red, green, blue}`." ) rgb_tuple = (red, green, blue) await self.send_colour_response(ctx, rgb_tuple) @@ -81,9 +81,10 @@ class Colour(commands.Cog): @colour.command() async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: """Command to create an embed from an HSV input.""" - if (hue or saturation or value) > 250 or (hue or saturation or value) < 0: + if (hue not in range(0, 361)) or any(c not in range(0, 101) for c in (saturation, value)): raise BadArgument( - message=f"HSV values can only be from 0 to 250. User input was: `{hue, saturation, value}`." + message="Hue can only be from 0 to 360. Saturation and Value can only be from 0 to 100. " + f"User input was: `{hue, saturation, value}`." ) # ImageColor HSV expects S and V to be swapped hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {value}%, {saturation}%)") @@ -92,9 +93,10 @@ class Colour(commands.Cog): @colour.command() async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: """Command to create an embed from an HSL input.""" - if (hue or saturation or lightness) > 360 or (hue or saturation or lightness) < 0: + if (hue not in range(0, 361)) or any(c not in range(0, 101) for c in (saturation, lightness)): raise BadArgument( - message=f"HSL values can only be from 0 to 360. User input was: `{hue, saturation, lightness}`." + message="Hue can only be from 0 to 360. Saturation and Lightness can only be from 0 to 100. " + f"User input was: `{hue, saturation, lightness}`." ) hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") await self.send_colour_response(ctx, hsl_tuple) @@ -102,7 +104,7 @@ class Colour(commands.Cog): @colour.command() async def cmyk(self, ctx: commands.Context, cyan: int, magenta: int, yellow: int, key: int) -> None: """Command to create an embed from a CMYK input.""" - if (cyan or magenta or yellow or key) > 100 or (cyan or magenta or yellow or key) < 0: + if any(c not in range(0, 101) for c in (cyan or magenta or yellow or key)): raise BadArgument( message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`." ) @@ -191,7 +193,7 @@ class Colour(commands.Cog): ) colour_name = [name for name, hex_code in self.colour_mapping.items() if hex_code == match][0] except TypeError: - colour_name = None + colour_name = "No match found" return colour_name def match_colour_name(self, input_colour_name: str) -> str: @@ -204,7 +206,6 @@ class Colour(commands.Cog): ) hex_match = f"#{self.colour_mapping[match]}" except (ValueError, TypeError): - hex_match = None raise BadArgument(message=f"No color found for: `{input_colour_name}`") return hex_match -- cgit v1.2.3 From 930c9649eaec79cebfa2969501b4614ccd26d069 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Fri, 29 Oct 2021 07:03:59 -0400 Subject: chore: add some conditions for the embed variables --- bot/exts/utilities/color.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 587d4e18..a21b74e4 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -28,16 +28,25 @@ class Colour(commands.Cog): """Create and send embed from user given colour information.""" r, g, b = rgb name = self._rgb_to_name(rgb) - colour_mode = ctx.invoked_with + if name == "No match found": + name = None + try: colour_or_color = ctx.invoked_parents[0] except IndexError: colour_or_color = "colour" - input_colour = ctx.args[2:][0] + + colour_mode = ctx.invoked_with + if colour_mode == "random": + input_colour = "random" + else: + input_colour = ctx.args[2:][0] + if colour_mode not in ("name", "hex", "random"): colour_mode = colour_mode.upper() else: colour_mode = colour_mode.title() + desc = f"{colour_mode} information for `{name or input_colour}`." colour_embed = Embed( title=colour_or_color.title(), -- cgit v1.2.3 From 409434dbf9c14fb47d20c1329f86a3863f19a2be Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Fri, 29 Oct 2021 15:49:57 -0400 Subject: fix: iterate through tuple Co-authored-by: Sn4u <35849006+Sn4u@users.noreply.github.com> --- bot/exts/utilities/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index a21b74e4..035f7d6a 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -113,7 +113,7 @@ class Colour(commands.Cog): @colour.command() async def cmyk(self, ctx: commands.Context, cyan: int, magenta: int, yellow: int, key: int) -> None: """Command to create an embed from a CMYK input.""" - if any(c not in range(0, 101) for c in (cyan or magenta or yellow or key)): + if any(c not in range(0, 101) for c in (cyan, magenta, yellow, key)): raise BadArgument( message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`." ) -- cgit v1.2.3 From 88089140dab024b43428b31ef7c729219ff19b4a Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Fri, 29 Oct 2021 21:00:35 -0400 Subject: fix: invoke without command, hsv fix --- bot/exts/utilities/color.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 035f7d6a..5f5708b2 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -39,10 +39,12 @@ class Colour(commands.Cog): colour_mode = ctx.invoked_with if colour_mode == "random": input_colour = "random" + elif colour_mode in ("colour", "color"): + input_colour = rgb else: input_colour = ctx.args[2:][0] - if colour_mode not in ("name", "hex", "random"): + if colour_mode not in ("name", "hex", "random", "color", "colour"): colour_mode = colour_mode.upper() else: colour_mode = colour_mode.title() @@ -71,11 +73,15 @@ class Colour(commands.Cog): await ctx.send(file=thumbnail_file, embed=colour_embed) - @commands.group(aliases=("color",)) - async def colour(self, ctx: commands.Context) -> None: + @commands.group(aliases=("color",), invoke_without_command=True) + async def colour(self, ctx: commands.Context, *, extra: str) -> None: """User initiated command to create an embed that displays colour information.""" if ctx.invoked_subcommand is None: - await invoke_help_command(ctx) + try: + extra_colour = ImageColor.getrgb(extra) + await self.send_colour_response(ctx, extra_colour) + except ValueError: + await invoke_help_command(ctx) @colour.command() async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: @@ -95,8 +101,7 @@ class Colour(commands.Cog): message="Hue can only be from 0 to 360. Saturation and Value can only be from 0 to 100. " f"User input was: `{hue, saturation, value}`." ) - # ImageColor HSV expects S and V to be swapped - hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {value}%, {saturation}%)") + hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") await self.send_colour_response(ctx, hsv_tuple) @colour.command() @@ -158,8 +163,8 @@ class Colour(commands.Cog): @staticmethod def _rgb_to_hsv(rgb: tuple[int, int, int]) -> tuple[int, int, int]: """Convert RGB values to HSV values.""" - rgb_list = [val / 255.0 for val in rgb] - h, v, s = colorsys.rgb_to_hsv(*rgb_list) + rgb_list = [val / 255 for val in rgb] + h, s, v = colorsys.rgb_to_hsv(*rgb_list) hsv = (round(h * 360), round(s * 100), round(v * 100)) return hsv -- cgit v1.2.3 From eac9d3b4194004894d4f4e50862f184db695d402 Mon Sep 17 00:00:00 2001 From: CyberCitizen01 Date: Mon, 1 Nov 2021 20:17:15 +0530 Subject: Add source credit and Color.from_rgb method to get discord color https://github.com/python-discord/sir-lancebot/pull/842/files#r739619938 https://github.com/python-discord/sir-lancebot/pull/842/files#r739295253 --- bot/exts/utilities/color.py | 6 +++--- bot/resources/utilities/ryanzec_colours.json | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 5f5708b2..563376fe 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -5,7 +5,7 @@ import random from io import BytesIO from PIL import Image, ImageColor -from discord import Embed, File +from discord import Embed, File, Color from discord.ext import commands from discord.ext.commands.errors import BadArgument from rapidfuzz import process @@ -23,10 +23,10 @@ class Colour(commands.Cog): self.bot = bot with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: self.colour_mapping = json.load(f) + del self.colour_mapping['_'] # Delete source credit entry async def send_colour_response(self, ctx: commands.Context, rgb: tuple[int, int, int]) -> None: """Create and send embed from user given colour information.""" - r, g, b = rgb name = self._rgb_to_name(rgb) if name == "No match found": name = None @@ -53,7 +53,7 @@ class Colour(commands.Cog): colour_embed = Embed( title=colour_or_color.title(), description=desc, - colour=int(f"{r:02x}{g:02x}{b:02x}", 16) + colour= Color.from_rgb(*rgb) ) colour_conversions = self.get_colour_conversions(rgb) for colour_space, value in colour_conversions.items(): diff --git a/bot/resources/utilities/ryanzec_colours.json b/bot/resources/utilities/ryanzec_colours.json index 7b89f052..ad8f05fd 100644 --- a/bot/resources/utilities/ryanzec_colours.json +++ b/bot/resources/utilities/ryanzec_colours.json @@ -1,4 +1,5 @@ { + "_": "Source: https://github.com/ryanzec/name-that-color/blob/0bb5ec7f37e4f6e7f2c164f39f7f08cca7c8e621/lib/ntc.js#L116-L1681", "Abbey": "4C4F56", "Acadia": "1B1404", "Acapulco": "7CB0A1", -- cgit v1.2.3 From 15a346bc726f6ca9075f6692b6deb2f486cc66aa Mon Sep 17 00:00:00 2001 From: CyberCitizen01 Date: Mon, 1 Nov 2021 20:23:09 +0530 Subject: Fix lint errors of eac9d3b4194004894d4f4e50862f184db695d402 --- bot/exts/utilities/color.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 563376fe..c79e7620 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -5,7 +5,7 @@ import random from io import BytesIO from PIL import Image, ImageColor -from discord import Embed, File, Color +from discord import Color, Embed, File from discord.ext import commands from discord.ext.commands.errors import BadArgument from rapidfuzz import process @@ -23,7 +23,7 @@ class Colour(commands.Cog): self.bot = bot with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: self.colour_mapping = json.load(f) - del self.colour_mapping['_'] # Delete source credit entry + del self.colour_mapping['_'] # Delete source credit entry async def send_colour_response(self, ctx: commands.Context, rgb: tuple[int, int, int]) -> None: """Create and send embed from user given colour information.""" @@ -53,7 +53,7 @@ class Colour(commands.Cog): colour_embed = Embed( title=colour_or_color.title(), description=desc, - colour= Color.from_rgb(*rgb) + colour=Color.from_rgb(*rgb) ) colour_conversions = self.get_colour_conversions(rgb) for colour_space, value in colour_conversions.items(): -- cgit v1.2.3 From c869faf32ceeadc94a8a38b5370706a4b6da32ae Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Wed, 3 Nov 2021 20:40:15 -0400 Subject: fix: return None if no name match is found --- bot/exts/utilities/color.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index c79e7620..9c801413 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -28,8 +28,8 @@ class Colour(commands.Cog): async def send_colour_response(self, ctx: commands.Context, rgb: tuple[int, int, int]) -> None: """Create and send embed from user given colour information.""" name = self._rgb_to_name(rgb) - if name == "No match found": - name = None + if name is None: + name = "No match found" try: colour_or_color = ctx.invoked_parents[0] @@ -207,7 +207,7 @@ class Colour(commands.Cog): ) colour_name = [name for name, hex_code in self.colour_mapping.items() if hex_code == match][0] except TypeError: - colour_name = "No match found" + colour_name = None return colour_name def match_colour_name(self, input_colour_name: str) -> str: -- cgit v1.2.3 From 6869fa330ea67bced27df68d3b730e5fdbdb7aa4 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Wed, 3 Nov 2021 21:01:36 -0400 Subject: fix: use and handle conversions with name value --- bot/exts/utilities/color.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 9c801413..56036cd0 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -151,13 +151,16 @@ class Colour(commands.Cog): def get_colour_conversions(self, rgb: tuple[int, int, int]) -> dict[str, str]: """Create a dictionary mapping of colour types and their values.""" + colour_name = self._rgb_to_name(rgb) + if colour_name is None: + colour_name = "No match found" return { "RGB": rgb, "HSV": self._rgb_to_hsv(rgb), "HSL": self._rgb_to_hsl(rgb), "CMYK": self._rgb_to_cmyk(rgb), "Hex": self._rgb_to_hex(rgb), - "Name": self._rgb_to_name(rgb) + "Name": colour_name } @staticmethod @@ -182,7 +185,7 @@ class Colour(commands.Cog): rgb_list = [val / 255.0 for val in rgb] if not any(rgb_list): return 0, 0, 0, 100 - k = 1 - max(val for val in rgb_list) + k = 1 - max(rgb_list) c = round((1 - rgb_list[0] - k) * 100 / (1 - k)) m = round((1 - rgb_list[1] - k) * 100 / (1 - k)) y = round((1 - rgb_list[2] - k) * 100 / (1 - k)) -- cgit v1.2.3 From 3280188ecb025c64597484b145aff9c19916a4e6 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Wed, 3 Nov 2021 21:02:20 -0400 Subject: fix: have description in-line --- bot/exts/utilities/color.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 56036cd0..2e18884c 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -49,10 +49,9 @@ class Colour(commands.Cog): else: colour_mode = colour_mode.title() - desc = f"{colour_mode} information for `{name or input_colour}`." colour_embed = Embed( title=colour_or_color.title(), - description=desc, + description=f"{colour_mode} information for `{name or input_colour}`.", colour=Color.from_rgb(*rgb) ) colour_conversions = self.get_colour_conversions(rgb) -- cgit v1.2.3 From a66cdd48784baf948e294b10f7f81746c0c4452d Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Wed, 3 Nov 2021 21:08:50 -0400 Subject: fix: reduce 'from' imports --- bot/exts/utilities/color.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py index 2e18884c..5ff1e2b8 100644 --- a/bot/exts/utilities/color.py +++ b/bot/exts/utilities/color.py @@ -4,11 +4,10 @@ import pathlib import random from io import BytesIO +import discord +import rapidfuzz from PIL import Image, ImageColor -from discord import Color, Embed, File from discord.ext import commands -from discord.ext.commands.errors import BadArgument -from rapidfuzz import process from bot.bot import Bot from bot.exts.core.extensions import invoke_help_command @@ -49,10 +48,10 @@ class Colour(commands.Cog): else: colour_mode = colour_mode.title() - colour_embed = Embed( + colour_embed = discord.Embed( title=colour_or_color.title(), description=f"{colour_mode} information for `{name or input_colour}`.", - colour=Color.from_rgb(*rgb) + colour=discord.Color.from_rgb(*rgb) ) colour_conversions = self.get_colour_conversions(rgb) for colour_space, value in colour_conversions.items(): @@ -66,7 +65,7 @@ class Colour(commands.Cog): buffer = BytesIO() thumbnail.save(buffer, "PNG") buffer.seek(0) - thumbnail_file = File(buffer, filename="colour.png") + thumbnail_file = discord.File(buffer, filename="colour.png") colour_embed.set_thumbnail(url="attachment://colour.png") @@ -86,7 +85,7 @@ class Colour(commands.Cog): async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: """Command to create an embed from an RGB input.""" if any(c not in range(0, 256) for c in (red, green, blue)): - raise BadArgument( + raise commands.BadArgument( message=f"RGB values can only be from 0 to 255. User input was: `{red, green, blue}`." ) rgb_tuple = (red, green, blue) @@ -96,7 +95,7 @@ class Colour(commands.Cog): async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: """Command to create an embed from an HSV input.""" if (hue not in range(0, 361)) or any(c not in range(0, 101) for c in (saturation, value)): - raise BadArgument( + raise commands.BadArgument( message="Hue can only be from 0 to 360. Saturation and Value can only be from 0 to 100. " f"User input was: `{hue, saturation, value}`." ) @@ -107,7 +106,7 @@ class Colour(commands.Cog): async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: """Command to create an embed from an HSL input.""" if (hue not in range(0, 361)) or any(c not in range(0, 101) for c in (saturation, lightness)): - raise BadArgument( + raise commands.BadArgument( message="Hue can only be from 0 to 360. Saturation and Lightness can only be from 0 to 100. " f"User input was: `{hue, saturation, lightness}`." ) @@ -118,7 +117,7 @@ class Colour(commands.Cog): async def cmyk(self, ctx: commands.Context, cyan: int, magenta: int, yellow: int, key: int) -> None: """Command to create an embed from a CMYK input.""" if any(c not in range(0, 101) for c in (cyan, magenta, yellow, key)): - raise BadArgument( + raise commands.BadArgument( message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`." ) r = round(255 * (1 - (cyan / 100)) * (1 - (key / 100))) @@ -202,7 +201,7 @@ class Colour(commands.Cog): """Convert RGB values to a fuzzy matched name.""" input_hex_colour = self._rgb_to_hex(rgb) try: - match, certainty, _ = process.extractOne( + match, certainty, _ = rapidfuzz.process.extractOne( query=input_hex_colour, choices=self.colour_mapping.values(), score_cutoff=80 @@ -215,14 +214,14 @@ class Colour(commands.Cog): def match_colour_name(self, input_colour_name: str) -> str: """Convert a colour name to HEX code.""" try: - match, certainty, _ = process.extractOne( + match, certainty, _ = rapidfuzz.process.extractOne( query=input_colour_name, choices=self.colour_mapping.keys(), score_cutoff=80 ) hex_match = f"#{self.colour_mapping[match]}" except (ValueError, TypeError): - raise BadArgument(message=f"No color found for: `{input_colour_name}`") + raise commands.BadArgument(message=f"No color found for: `{input_colour_name}`") return hex_match -- cgit v1.2.3 From edeb48ab0489c0ac0fcbf0a3a45dde11236f613c Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Wed, 3 Nov 2021 21:13:24 -0400 Subject: rename to colour --- bot/exts/utilities/color.py | 230 ------------------------------------------- bot/exts/utilities/colour.py | 230 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+), 230 deletions(-) delete mode 100644 bot/exts/utilities/color.py create mode 100644 bot/exts/utilities/colour.py (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py deleted file mode 100644 index 5ff1e2b8..00000000 --- a/bot/exts/utilities/color.py +++ /dev/null @@ -1,230 +0,0 @@ -import colorsys -import json -import pathlib -import random -from io import BytesIO - -import discord -import rapidfuzz -from PIL import Image, ImageColor -from discord.ext import commands - -from bot.bot import Bot -from bot.exts.core.extensions import invoke_help_command - -THUMBNAIL_SIZE = (80, 80) - - -class Colour(commands.Cog): - """Cog for the Colour command.""" - - def __init__(self, bot: Bot): - self.bot = bot - with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: - self.colour_mapping = json.load(f) - del self.colour_mapping['_'] # Delete source credit entry - - async def send_colour_response(self, ctx: commands.Context, rgb: tuple[int, int, int]) -> None: - """Create and send embed from user given colour information.""" - name = self._rgb_to_name(rgb) - if name is None: - name = "No match found" - - try: - colour_or_color = ctx.invoked_parents[0] - except IndexError: - colour_or_color = "colour" - - colour_mode = ctx.invoked_with - if colour_mode == "random": - input_colour = "random" - elif colour_mode in ("colour", "color"): - input_colour = rgb - else: - input_colour = ctx.args[2:][0] - - if colour_mode not in ("name", "hex", "random", "color", "colour"): - colour_mode = colour_mode.upper() - else: - colour_mode = colour_mode.title() - - colour_embed = discord.Embed( - title=colour_or_color.title(), - description=f"{colour_mode} information for `{name or input_colour}`.", - colour=discord.Color.from_rgb(*rgb) - ) - colour_conversions = self.get_colour_conversions(rgb) - for colour_space, value in colour_conversions.items(): - colour_embed.add_field( - name=colour_space, - value=f"`{value}`", - inline=True - ) - - thumbnail = Image.new("RGB", THUMBNAIL_SIZE, color=rgb) - buffer = BytesIO() - thumbnail.save(buffer, "PNG") - buffer.seek(0) - thumbnail_file = discord.File(buffer, filename="colour.png") - - colour_embed.set_thumbnail(url="attachment://colour.png") - - await ctx.send(file=thumbnail_file, embed=colour_embed) - - @commands.group(aliases=("color",), invoke_without_command=True) - async def colour(self, ctx: commands.Context, *, extra: str) -> None: - """User initiated command to create an embed that displays colour information.""" - if ctx.invoked_subcommand is None: - try: - extra_colour = ImageColor.getrgb(extra) - await self.send_colour_response(ctx, extra_colour) - except ValueError: - await invoke_help_command(ctx) - - @colour.command() - async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: - """Command to create an embed from an RGB input.""" - if any(c not in range(0, 256) for c in (red, green, blue)): - raise commands.BadArgument( - message=f"RGB values can only be from 0 to 255. User input was: `{red, green, blue}`." - ) - rgb_tuple = (red, green, blue) - await self.send_colour_response(ctx, rgb_tuple) - - @colour.command() - async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: - """Command to create an embed from an HSV input.""" - if (hue not in range(0, 361)) or any(c not in range(0, 101) for c in (saturation, value)): - raise commands.BadArgument( - message="Hue can only be from 0 to 360. Saturation and Value can only be from 0 to 100. " - f"User input was: `{hue, saturation, value}`." - ) - hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") - await self.send_colour_response(ctx, hsv_tuple) - - @colour.command() - async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: - """Command to create an embed from an HSL input.""" - if (hue not in range(0, 361)) or any(c not in range(0, 101) for c in (saturation, lightness)): - raise commands.BadArgument( - message="Hue can only be from 0 to 360. Saturation and Lightness can only be from 0 to 100. " - f"User input was: `{hue, saturation, lightness}`." - ) - hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") - await self.send_colour_response(ctx, hsl_tuple) - - @colour.command() - async def cmyk(self, ctx: commands.Context, cyan: int, magenta: int, yellow: int, key: int) -> None: - """Command to create an embed from a CMYK input.""" - if any(c not in range(0, 101) for c in (cyan, magenta, yellow, key)): - raise commands.BadArgument( - message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`." - ) - r = round(255 * (1 - (cyan / 100)) * (1 - (key / 100))) - g = round(255 * (1 - (magenta / 100)) * (1 - (key / 100))) - b = round(255 * (1 - (yellow / 100)) * (1 - (key / 100))) - await self.send_colour_response(ctx, (r, g, b)) - - @colour.command() - async def hex(self, ctx: commands.Context, hex_code: str) -> None: - """Command to create an embed from a HEX input.""" - if "#" not in hex_code: - hex_code = f"#{hex_code}" - hex_tuple = ImageColor.getrgb(hex_code) - await self.send_colour_response(ctx, hex_tuple) - - @colour.command() - async def name(self, ctx: commands.Context, user_colour_name: str) -> None: - """Command to create an embed from a name input.""" - hex_colour = self.match_colour_name(user_colour_name) - hex_tuple = ImageColor.getrgb(hex_colour) - await self.send_colour_response(ctx, hex_tuple) - - @colour.command() - async def random(self, ctx: commands.Context) -> None: - """Command to create an embed from a randomly chosen colour from the reference file.""" - hex_colour = random.choice(list(self.colour_mapping.values())) - hex_tuple = ImageColor.getrgb(f"#{hex_colour}") - await self.send_colour_response(ctx, hex_tuple) - - def get_colour_conversions(self, rgb: tuple[int, int, int]) -> dict[str, str]: - """Create a dictionary mapping of colour types and their values.""" - colour_name = self._rgb_to_name(rgb) - if colour_name is None: - colour_name = "No match found" - return { - "RGB": rgb, - "HSV": self._rgb_to_hsv(rgb), - "HSL": self._rgb_to_hsl(rgb), - "CMYK": self._rgb_to_cmyk(rgb), - "Hex": self._rgb_to_hex(rgb), - "Name": colour_name - } - - @staticmethod - def _rgb_to_hsv(rgb: tuple[int, int, int]) -> tuple[int, int, int]: - """Convert RGB values to HSV values.""" - rgb_list = [val / 255 for val in rgb] - h, s, v = colorsys.rgb_to_hsv(*rgb_list) - hsv = (round(h * 360), round(s * 100), round(v * 100)) - return hsv - - @staticmethod - def _rgb_to_hsl(rgb: tuple[int, int, int]) -> tuple[int, int, int]: - """Convert RGB values to HSL values.""" - rgb_list = [val / 255.0 for val in rgb] - h, l, s = colorsys.rgb_to_hls(*rgb_list) - hsl = (round(h * 360), round(s * 100), round(l * 100)) - return hsl - - @staticmethod - def _rgb_to_cmyk(rgb: tuple[int, int, int, int]) -> tuple[int, int, int, int]: - """Convert RGB values to CMYK values.""" - rgb_list = [val / 255.0 for val in rgb] - if not any(rgb_list): - return 0, 0, 0, 100 - k = 1 - max(rgb_list) - c = round((1 - rgb_list[0] - k) * 100 / (1 - k)) - m = round((1 - rgb_list[1] - k) * 100 / (1 - k)) - y = round((1 - rgb_list[2] - k) * 100 / (1 - k)) - cmyk = (c, m, y, round(k * 100)) - return cmyk - - @staticmethod - def _rgb_to_hex(rgb: tuple[int, int, int]) -> str: - """Convert RGB values to HEX code.""" - hex_ = ''.join([hex(val)[2:].zfill(2) for val in rgb]) - hex_code = f"#{hex_}".upper() - return hex_code - - def _rgb_to_name(self, rgb: tuple[int, int, int]) -> str: - """Convert RGB values to a fuzzy matched name.""" - input_hex_colour = self._rgb_to_hex(rgb) - try: - match, certainty, _ = rapidfuzz.process.extractOne( - query=input_hex_colour, - choices=self.colour_mapping.values(), - score_cutoff=80 - ) - colour_name = [name for name, hex_code in self.colour_mapping.items() if hex_code == match][0] - except TypeError: - colour_name = None - return colour_name - - def match_colour_name(self, input_colour_name: str) -> str: - """Convert a colour name to HEX code.""" - try: - match, certainty, _ = rapidfuzz.process.extractOne( - query=input_colour_name, - choices=self.colour_mapping.keys(), - score_cutoff=80 - ) - hex_match = f"#{self.colour_mapping[match]}" - except (ValueError, TypeError): - raise commands.BadArgument(message=f"No color found for: `{input_colour_name}`") - return hex_match - - -def setup(bot: Bot) -> None: - """Load the Colour cog.""" - bot.add_cog(Colour(bot)) diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py new file mode 100644 index 00000000..5ff1e2b8 --- /dev/null +++ b/bot/exts/utilities/colour.py @@ -0,0 +1,230 @@ +import colorsys +import json +import pathlib +import random +from io import BytesIO + +import discord +import rapidfuzz +from PIL import Image, ImageColor +from discord.ext import commands + +from bot.bot import Bot +from bot.exts.core.extensions import invoke_help_command + +THUMBNAIL_SIZE = (80, 80) + + +class Colour(commands.Cog): + """Cog for the Colour command.""" + + def __init__(self, bot: Bot): + self.bot = bot + with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f: + self.colour_mapping = json.load(f) + del self.colour_mapping['_'] # Delete source credit entry + + async def send_colour_response(self, ctx: commands.Context, rgb: tuple[int, int, int]) -> None: + """Create and send embed from user given colour information.""" + name = self._rgb_to_name(rgb) + if name is None: + name = "No match found" + + try: + colour_or_color = ctx.invoked_parents[0] + except IndexError: + colour_or_color = "colour" + + colour_mode = ctx.invoked_with + if colour_mode == "random": + input_colour = "random" + elif colour_mode in ("colour", "color"): + input_colour = rgb + else: + input_colour = ctx.args[2:][0] + + if colour_mode not in ("name", "hex", "random", "color", "colour"): + colour_mode = colour_mode.upper() + else: + colour_mode = colour_mode.title() + + colour_embed = discord.Embed( + title=colour_or_color.title(), + description=f"{colour_mode} information for `{name or input_colour}`.", + colour=discord.Color.from_rgb(*rgb) + ) + colour_conversions = self.get_colour_conversions(rgb) + for colour_space, value in colour_conversions.items(): + colour_embed.add_field( + name=colour_space, + value=f"`{value}`", + inline=True + ) + + thumbnail = Image.new("RGB", THUMBNAIL_SIZE, color=rgb) + buffer = BytesIO() + thumbnail.save(buffer, "PNG") + buffer.seek(0) + thumbnail_file = discord.File(buffer, filename="colour.png") + + colour_embed.set_thumbnail(url="attachment://colour.png") + + await ctx.send(file=thumbnail_file, embed=colour_embed) + + @commands.group(aliases=("color",), invoke_without_command=True) + async def colour(self, ctx: commands.Context, *, extra: str) -> None: + """User initiated command to create an embed that displays colour information.""" + if ctx.invoked_subcommand is None: + try: + extra_colour = ImageColor.getrgb(extra) + await self.send_colour_response(ctx, extra_colour) + except ValueError: + await invoke_help_command(ctx) + + @colour.command() + async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: + """Command to create an embed from an RGB input.""" + if any(c not in range(0, 256) for c in (red, green, blue)): + raise commands.BadArgument( + message=f"RGB values can only be from 0 to 255. User input was: `{red, green, blue}`." + ) + rgb_tuple = (red, green, blue) + await self.send_colour_response(ctx, rgb_tuple) + + @colour.command() + async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: + """Command to create an embed from an HSV input.""" + if (hue not in range(0, 361)) or any(c not in range(0, 101) for c in (saturation, value)): + raise commands.BadArgument( + message="Hue can only be from 0 to 360. Saturation and Value can only be from 0 to 100. " + f"User input was: `{hue, saturation, value}`." + ) + hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)") + await self.send_colour_response(ctx, hsv_tuple) + + @colour.command() + async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: + """Command to create an embed from an HSL input.""" + if (hue not in range(0, 361)) or any(c not in range(0, 101) for c in (saturation, lightness)): + raise commands.BadArgument( + message="Hue can only be from 0 to 360. Saturation and Lightness can only be from 0 to 100. " + f"User input was: `{hue, saturation, lightness}`." + ) + hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)") + await self.send_colour_response(ctx, hsl_tuple) + + @colour.command() + async def cmyk(self, ctx: commands.Context, cyan: int, magenta: int, yellow: int, key: int) -> None: + """Command to create an embed from a CMYK input.""" + if any(c not in range(0, 101) for c in (cyan, magenta, yellow, key)): + raise commands.BadArgument( + message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`." + ) + r = round(255 * (1 - (cyan / 100)) * (1 - (key / 100))) + g = round(255 * (1 - (magenta / 100)) * (1 - (key / 100))) + b = round(255 * (1 - (yellow / 100)) * (1 - (key / 100))) + await self.send_colour_response(ctx, (r, g, b)) + + @colour.command() + async def hex(self, ctx: commands.Context, hex_code: str) -> None: + """Command to create an embed from a HEX input.""" + if "#" not in hex_code: + hex_code = f"#{hex_code}" + hex_tuple = ImageColor.getrgb(hex_code) + await self.send_colour_response(ctx, hex_tuple) + + @colour.command() + async def name(self, ctx: commands.Context, user_colour_name: str) -> None: + """Command to create an embed from a name input.""" + hex_colour = self.match_colour_name(user_colour_name) + hex_tuple = ImageColor.getrgb(hex_colour) + await self.send_colour_response(ctx, hex_tuple) + + @colour.command() + async def random(self, ctx: commands.Context) -> None: + """Command to create an embed from a randomly chosen colour from the reference file.""" + hex_colour = random.choice(list(self.colour_mapping.values())) + hex_tuple = ImageColor.getrgb(f"#{hex_colour}") + await self.send_colour_response(ctx, hex_tuple) + + def get_colour_conversions(self, rgb: tuple[int, int, int]) -> dict[str, str]: + """Create a dictionary mapping of colour types and their values.""" + colour_name = self._rgb_to_name(rgb) + if colour_name is None: + colour_name = "No match found" + return { + "RGB": rgb, + "HSV": self._rgb_to_hsv(rgb), + "HSL": self._rgb_to_hsl(rgb), + "CMYK": self._rgb_to_cmyk(rgb), + "Hex": self._rgb_to_hex(rgb), + "Name": colour_name + } + + @staticmethod + def _rgb_to_hsv(rgb: tuple[int, int, int]) -> tuple[int, int, int]: + """Convert RGB values to HSV values.""" + rgb_list = [val / 255 for val in rgb] + h, s, v = colorsys.rgb_to_hsv(*rgb_list) + hsv = (round(h * 360), round(s * 100), round(v * 100)) + return hsv + + @staticmethod + def _rgb_to_hsl(rgb: tuple[int, int, int]) -> tuple[int, int, int]: + """Convert RGB values to HSL values.""" + rgb_list = [val / 255.0 for val in rgb] + h, l, s = colorsys.rgb_to_hls(*rgb_list) + hsl = (round(h * 360), round(s * 100), round(l * 100)) + return hsl + + @staticmethod + def _rgb_to_cmyk(rgb: tuple[int, int, int, int]) -> tuple[int, int, int, int]: + """Convert RGB values to CMYK values.""" + rgb_list = [val / 255.0 for val in rgb] + if not any(rgb_list): + return 0, 0, 0, 100 + k = 1 - max(rgb_list) + c = round((1 - rgb_list[0] - k) * 100 / (1 - k)) + m = round((1 - rgb_list[1] - k) * 100 / (1 - k)) + y = round((1 - rgb_list[2] - k) * 100 / (1 - k)) + cmyk = (c, m, y, round(k * 100)) + return cmyk + + @staticmethod + def _rgb_to_hex(rgb: tuple[int, int, int]) -> str: + """Convert RGB values to HEX code.""" + hex_ = ''.join([hex(val)[2:].zfill(2) for val in rgb]) + hex_code = f"#{hex_}".upper() + return hex_code + + def _rgb_to_name(self, rgb: tuple[int, int, int]) -> str: + """Convert RGB values to a fuzzy matched name.""" + input_hex_colour = self._rgb_to_hex(rgb) + try: + match, certainty, _ = rapidfuzz.process.extractOne( + query=input_hex_colour, + choices=self.colour_mapping.values(), + score_cutoff=80 + ) + colour_name = [name for name, hex_code in self.colour_mapping.items() if hex_code == match][0] + except TypeError: + colour_name = None + return colour_name + + def match_colour_name(self, input_colour_name: str) -> str: + """Convert a colour name to HEX code.""" + try: + match, certainty, _ = rapidfuzz.process.extractOne( + query=input_colour_name, + choices=self.colour_mapping.keys(), + score_cutoff=80 + ) + hex_match = f"#{self.colour_mapping[match]}" + except (ValueError, TypeError): + raise commands.BadArgument(message=f"No color found for: `{input_colour_name}`") + return hex_match + + +def setup(bot: Bot) -> None: + """Load the Colour cog.""" + bot.add_cog(Colour(bot)) -- cgit v1.2.3 From 2cd5aa83a722f4137e32514dfe9c45688be365ac Mon Sep 17 00:00:00 2001 From: NipaDev <60810623+Nipa-Code@users.noreply.github.com> Date: Fri, 5 Nov 2021 03:18:11 +0200 Subject: Add option to get specific amount of realpython articles Add a feature to choice in between 1-5 (inclusive) articles. If value not specified, the default, 5 will be used. Co-authored-by: ChrisJL --- bot/exts/utilities/realpython.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/realpython.py b/bot/exts/utilities/realpython.py index ef8b2638..bf8f1341 100644 --- a/bot/exts/utilities/realpython.py +++ b/bot/exts/utilities/realpython.py @@ -1,5 +1,6 @@ import logging from html import unescape +from typing import Optional from urllib.parse import quote_plus from discord import Embed @@ -31,9 +32,18 @@ class RealPython(commands.Cog): @commands.command(aliases=["rp"]) @commands.cooldown(1, 10, commands.cooldowns.BucketType.user) - async def realpython(self, ctx: commands.Context, *, user_search: str) -> None: - """Send 5 articles that match the user's search terms.""" - params = {"q": user_search, "limit": 5, "kind": "article"} + async def realpython(self, ctx: commands.Context, amount: Optional[int] = 5, *, user_search: str) -> None: + """ + Send some articles from RealPython that match the search terms. + + By default the top 5 matches are sent, this can be overwritten to + a number between 1 and 5 by specifying an amount before the search query. + """ + if not 1 <= amount <= 5: + await ctx.send("`amount` must be between 1 and 5 (inclusive).") + return + + params = {"q": user_search, "limit": amount, "kind": "article"} async with self.bot.http_session.get(url=API_ROOT, params=params) as response: if response.status != 200: logger.error( -- cgit v1.2.3 From 726109fc47eec6259e6d8f8291b330e3353809e4 Mon Sep 17 00:00:00 2001 From: NipaDev <60810623+Nipa-Code@users.noreply.github.com> Date: Fri, 5 Nov 2021 12:49:20 +0200 Subject: Limit user reactions on embed pagination * Limit user reactions on embed pagination Limit user reactions to prevent non-author from removing message by adding user restriction to paginator. * Fixed the format of code to single line. Co-authored-by: ChrisJL --- bot/exts/utilities/wikipedia.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/wikipedia.py b/bot/exts/utilities/wikipedia.py index c5283de0..e5e8e289 100644 --- a/bot/exts/utilities/wikipedia.py +++ b/bot/exts/utilities/wikipedia.py @@ -86,9 +86,7 @@ class WikipediaSearch(commands.Cog): ) embed.set_thumbnail(url=WIKI_THUMBNAIL) embed.timestamp = datetime.utcnow() - await LinePaginator.paginate( - contents, ctx, embed - ) + await LinePaginator.paginate(contents, ctx, embed, restrict_to_user=ctx.author) else: await ctx.send( "Sorry, we could not find a wikipedia article using that search term." -- cgit v1.2.3 From a32fae52425fe6955be2be013f6149c90cc86e61 Mon Sep 17 00:00:00 2001 From: Shom770 <82843611+Shom770@users.noreply.github.com> Date: Sat, 6 Nov 2021 11:22:13 -0400 Subject: lowering challenges for compatibility with uppercase languages --- bot/exts/utilities/challenges.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/challenges.py b/bot/exts/utilities/challenges.py index e4738455..1f296390 100644 --- a/bot/exts/utilities/challenges.py +++ b/bot/exts/utilities/challenges.py @@ -278,7 +278,7 @@ class Challenges(commands.Cog): if language.lower() not in SUPPORTED_LANGUAGES["stable"] + SUPPORTED_LANGUAGES["beta"]: raise commands.BadArgument("This is not a recognized language on codewars.com!") - get_kata_link = f"https://codewars.com/kata/search/{language}" + get_kata_link = f"https://codewars.com/kata/search/{language.lower()}" params = {} if query is not None: -- cgit v1.2.3 From 7227fe279574ee0626ad6c7ead754c1212669af2 Mon Sep 17 00:00:00 2001 From: Shom770 <82843611+Shom770@users.noreply.github.com> Date: Sat, 6 Nov 2021 23:02:26 -0400 Subject: Change language to language.lower() --- bot/exts/utilities/challenges.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/challenges.py b/bot/exts/utilities/challenges.py index 1f296390..751ea2a3 100644 --- a/bot/exts/utilities/challenges.py +++ b/bot/exts/utilities/challenges.py @@ -275,10 +275,12 @@ class Challenges(commands.Cog): `.challenge , ` - Pulls a random challenge with the query provided, under that difficulty within the language's scope. """ - if language.lower() not in SUPPORTED_LANGUAGES["stable"] + SUPPORTED_LANGUAGES["beta"]: + + language = language.lower() + if language not in SUPPORTED_LANGUAGES["stable"] + SUPPORTED_LANGUAGES["beta"]: raise commands.BadArgument("This is not a recognized language on codewars.com!") - get_kata_link = f"https://codewars.com/kata/search/{language.lower()}" + get_kata_link = f"https://codewars.com/kata/search/{language}" params = {} if query is not None: -- cgit v1.2.3 From 65cfed425e19fa508c7d4e8736deb55c564cecbb Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Sat, 6 Nov 2021 23:07:31 -0400 Subject: Handle `.wtf` command without query (#939) Co-authored-by: Hedy Li Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/utilities/wtf_python.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/wtf_python.py b/bot/exts/utilities/wtf_python.py index 66a022d7..980b3dba 100644 --- a/bot/exts/utilities/wtf_python.py +++ b/bot/exts/utilities/wtf_python.py @@ -79,7 +79,7 @@ class WTFPython(commands.Cog): return match if certainty > MINIMUM_CERTAINTY else None @commands.command(aliases=("wtf", "WTF")) - async def wtf_python(self, ctx: commands.Context, *, query: str) -> None: + async def wtf_python(self, ctx: commands.Context, *, query: Optional[str] = None) -> None: """ Search WTF Python repository. @@ -87,6 +87,18 @@ class WTFPython(commands.Cog): Usage: --> .wtf wild imports """ + if query is None: + no_query_embed = Embed( + title="WTF Python?!", + colour=constants.Colours.dark_green, + description="A repository filled with suprising snippets that can make you say WTF?!\n\n" + f"[Go to the Repository]({BASE_URL})" + ) + logo = File(LOGO_PATH, filename="wtf_logo.jpg") + no_query_embed.set_thumbnail(url="attachment://wtf_logo.jpg") + await ctx.send(embed=no_query_embed, file=logo) + return + if len(query) > 50: embed = Embed( title=random.choice(constants.ERROR_REPLIES), -- cgit v1.2.3 From 1503845b4c5349e1a49c9c9ecdc37425b659e73b Mon Sep 17 00:00:00 2001 From: Shom770 <82843611+Shom770@users.noreply.github.com> Date: Sun, 7 Nov 2021 11:19:05 -0500 Subject: Fix line after function docstring --- bot/exts/utilities/challenges.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/challenges.py b/bot/exts/utilities/challenges.py index 751ea2a3..ab7ae442 100644 --- a/bot/exts/utilities/challenges.py +++ b/bot/exts/utilities/challenges.py @@ -275,7 +275,6 @@ class Challenges(commands.Cog): `.challenge , ` - Pulls a random challenge with the query provided, under that difficulty within the language's scope. """ - language = language.lower() if language not in SUPPORTED_LANGUAGES["stable"] + SUPPORTED_LANGUAGES["beta"]: raise commands.BadArgument("This is not a recognized language on codewars.com!") -- cgit v1.2.3 From 4fed12e92a6bb7280e6b69af07267dd82b0d6d0d Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Tue, 9 Nov 2021 08:03:20 -0500 Subject: fix: type hinting _rgb_to_cmyk --- bot/exts/utilities/colour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 5ff1e2b8..c2a5e7e7 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -178,7 +178,7 @@ class Colour(commands.Cog): return hsl @staticmethod - def _rgb_to_cmyk(rgb: tuple[int, int, int, int]) -> tuple[int, int, int, int]: + def _rgb_to_cmyk(rgb: tuple[int, int, int]) -> tuple[int, int, int, int]: """Convert RGB values to CMYK values.""" rgb_list = [val / 255.0 for val in rgb] if not any(rgb_list): -- cgit v1.2.3 From 2bb00fbf680d49c92bb40af71659489801cc0f99 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Tue, 9 Nov 2021 19:44:18 -0500 Subject: fix: handle alpha values in hex code Co-authored-by: Sn4u <35849006+Sn4u@users.noreply.github.com> --- bot/exts/utilities/colour.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index c2a5e7e7..7b128393 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -130,7 +130,13 @@ class Colour(commands.Cog): """Command to create an embed from a HEX input.""" if "#" not in hex_code: hex_code = f"#{hex_code}" + if len(hex_code) not in (4, 5, 7, 9) or any(_ not in string.hexdigits+"#" for _ in hex_code): + raise commands.BadArgument( + message=f"HEX values must be hexadecimal and take the form *#RRGGBB* or *#RGB*. User input was: `{hex_code}`.") + hex_tuple = ImageColor.getrgb(hex_code) + if len(hex_tuple) == 4: + hex_tuple = hex_tuple[:-1] # color must be RGB. If RGBA, we remove the alpha value await self.send_colour_response(ctx, hex_tuple) @colour.command() -- cgit v1.2.3 From 385cb3aacf60bdaea67fe59890e9c5d894f9d65a Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Tue, 9 Nov 2021 19:48:09 -0500 Subject: fix: add import, handle no name match in embed -Added `import string` to use the `string.hexdigits` method to check hex codes. -Handled bug where no name match found would be repeated in the embed in the first line as well as the value for the Name field. --- bot/exts/utilities/colour.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 7b128393..4528615b 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -2,6 +2,7 @@ import colorsys import json import pathlib import random +import string from io import BytesIO import discord @@ -27,8 +28,6 @@ class Colour(commands.Cog): async def send_colour_response(self, ctx: commands.Context, rgb: tuple[int, int, int]) -> None: """Create and send embed from user given colour information.""" name = self._rgb_to_name(rgb) - if name is None: - name = "No match found" try: colour_or_color = ctx.invoked_parents[0] @@ -132,7 +131,9 @@ class Colour(commands.Cog): hex_code = f"#{hex_code}" if len(hex_code) not in (4, 5, 7, 9) or any(_ not in string.hexdigits+"#" for _ in hex_code): raise commands.BadArgument( - message=f"HEX values must be hexadecimal and take the form *#RRGGBB* or *#RGB*. User input was: `{hex_code}`.") + message="HEX values must be hexadecimal and take the form *#RRGGBB* or *#RGB*. " + f"User input was: `{hex_code}`." + ) hex_tuple = ImageColor.getrgb(hex_code) if len(hex_tuple) == 4: -- cgit v1.2.3 From 57c13ff35801281b5e592c9f6c195093a2fc5c7b Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Tue, 9 Nov 2021 20:20:19 -0500 Subject: bug: handle multi word name entries and full input --- bot/exts/utilities/colour.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 4528615b..cede3312 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -39,8 +39,10 @@ class Colour(commands.Cog): input_colour = "random" elif colour_mode in ("colour", "color"): input_colour = rgb + elif colour_mode == "name": + input_colour = ctx.kwargs["user_colour_name"] else: - input_colour = ctx.args[2:][0] + input_colour = tuple(ctx.args[2:]) if colour_mode not in ("name", "hex", "random", "color", "colour"): colour_mode = colour_mode.upper() @@ -141,7 +143,7 @@ class Colour(commands.Cog): await self.send_colour_response(ctx, hex_tuple) @colour.command() - async def name(self, ctx: commands.Context, user_colour_name: str) -> None: + async def name(self, ctx: commands.Context, *, user_colour_name: str) -> None: """Command to create an embed from a name input.""" hex_colour = self.match_colour_name(user_colour_name) hex_tuple = ImageColor.getrgb(hex_colour) -- cgit v1.2.3 From 1de5e9b905310eb4745b9b5c83561dea3a289ea9 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Tue, 9 Nov 2021 20:39:39 -0500 Subject: chore: pull hex match out of try/except block --- bot/exts/utilities/colour.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index cede3312..656dddba 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -228,10 +228,9 @@ class Colour(commands.Cog): choices=self.colour_mapping.keys(), score_cutoff=80 ) - hex_match = f"#{self.colour_mapping[match]}" except (ValueError, TypeError): raise commands.BadArgument(message=f"No color found for: `{input_colour_name}`") - return hex_match + return f"#{self.colour_mapping[match]}" def setup(bot: Bot) -> None: -- cgit v1.2.3 From 93e6e96c89e621db6e463f4d5716533fd85cd96f Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Tue, 9 Nov 2021 20:44:28 -0500 Subject: chore: re-arrange command invocation with try/except --- bot/exts/utilities/colour.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 656dddba..e69930f4 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -75,12 +75,14 @@ class Colour(commands.Cog): @commands.group(aliases=("color",), invoke_without_command=True) async def colour(self, ctx: commands.Context, *, extra: str) -> None: """User initiated command to create an embed that displays colour information.""" - if ctx.invoked_subcommand is None: - try: - extra_colour = ImageColor.getrgb(extra) - await self.send_colour_response(ctx, extra_colour) - except ValueError: - await invoke_help_command(ctx) + if ctx.invoked_subcommand: + return + + try: + extra_colour = ImageColor.getrgb(extra) + await self.send_colour_response(ctx, extra_colour) + except ValueError: + await invoke_help_command(ctx) @colour.command() async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: -- cgit v1.2.3 From c70a4e7670805ca774d1c0d634e86dc6759d8669 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Tue, 9 Nov 2021 21:22:49 -0500 Subject: chore: remove 0 from range and change to " --- bot/exts/utilities/colour.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index e69930f4..9cbf3394 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -87,7 +87,7 @@ class Colour(commands.Cog): @colour.command() async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: """Command to create an embed from an RGB input.""" - if any(c not in range(0, 256) for c in (red, green, blue)): + if any(c not in range(256) for c in (red, green, blue)): raise commands.BadArgument( message=f"RGB values can only be from 0 to 255. User input was: `{red, green, blue}`." ) @@ -97,7 +97,7 @@ class Colour(commands.Cog): @colour.command() async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: """Command to create an embed from an HSV input.""" - if (hue not in range(0, 361)) or any(c not in range(0, 101) for c in (saturation, value)): + if (hue not in range(361)) or any(c not in range(101) for c in (saturation, value)): raise commands.BadArgument( message="Hue can only be from 0 to 360. Saturation and Value can only be from 0 to 100. " f"User input was: `{hue, saturation, value}`." @@ -108,7 +108,7 @@ class Colour(commands.Cog): @colour.command() async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: """Command to create an embed from an HSL input.""" - if (hue not in range(0, 361)) or any(c not in range(0, 101) for c in (saturation, lightness)): + if (hue not in range(361)) or any(c not in range(101) for c in (saturation, lightness)): raise commands.BadArgument( message="Hue can only be from 0 to 360. Saturation and Lightness can only be from 0 to 100. " f"User input was: `{hue, saturation, lightness}`." @@ -119,7 +119,7 @@ class Colour(commands.Cog): @colour.command() async def cmyk(self, ctx: commands.Context, cyan: int, magenta: int, yellow: int, key: int) -> None: """Command to create an embed from a CMYK input.""" - if any(c not in range(0, 101) for c in (cyan, magenta, yellow, key)): + if any(c not in range(101) for c in (cyan, magenta, yellow, key)): raise commands.BadArgument( message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`." ) @@ -204,7 +204,7 @@ class Colour(commands.Cog): @staticmethod def _rgb_to_hex(rgb: tuple[int, int, int]) -> str: """Convert RGB values to HEX code.""" - hex_ = ''.join([hex(val)[2:].zfill(2) for val in rgb]) + hex_ = "".join([hex(val)[2:].zfill(2) for val in rgb]) hex_code = f"#{hex_}".upper() return hex_code -- cgit v1.2.3 From 524d33a9909697386283d966dcda1dc712a16fb0 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Tue, 9 Nov 2021 21:55:49 -0500 Subject: fix: replace 'random' in embed with color mode --- bot/exts/utilities/colour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 9cbf3394..7e5065cd 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -36,7 +36,7 @@ class Colour(commands.Cog): colour_mode = ctx.invoked_with if colour_mode == "random": - input_colour = "random" + colour_mode = colour_or_color elif colour_mode in ("colour", "color"): input_colour = rgb elif colour_mode == "name": -- cgit v1.2.3 From 6f9da835444e0d357edc06839d9b98eafe72af30 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Wed, 10 Nov 2021 06:46:55 -0500 Subject: fix: handle user hex in embed --- bot/exts/utilities/colour.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 7e5065cd..ec7545cd 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -41,6 +41,8 @@ class Colour(commands.Cog): input_colour = rgb elif colour_mode == "name": input_colour = ctx.kwargs["user_colour_name"] + elif colour_mode == "hex": + input_colour = ctx.args[2:][0] else: input_colour = tuple(ctx.args[2:]) -- cgit v1.2.3 From b9b1d8c10e999e16cbd067dc49710634e23e6bd7 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Wed, 10 Nov 2021 06:48:05 -0500 Subject: fix: change kwarg to color_input instead of extra --- bot/exts/utilities/colour.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index ec7545cd..1e64993c 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -75,13 +75,13 @@ class Colour(commands.Cog): await ctx.send(file=thumbnail_file, embed=colour_embed) @commands.group(aliases=("color",), invoke_without_command=True) - async def colour(self, ctx: commands.Context, *, extra: str) -> None: + async def colour(self, ctx: commands.Context, *, color_input: str) -> None: """User initiated command to create an embed that displays colour information.""" if ctx.invoked_subcommand: return try: - extra_colour = ImageColor.getrgb(extra) + extra_colour = ImageColor.getrgb(color_input) await self.send_colour_response(ctx, extra_colour) except ValueError: await invoke_help_command(ctx) -- cgit v1.2.3 From 6b21ffaef0e545d1710f2703c450c5492e100be7 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 10 Nov 2021 08:37:18 -0500 Subject: test: UI/UX updates, not tested yet --- bot/exts/utilities/colour.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 1e64993c..259e3394 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -4,6 +4,7 @@ import pathlib import random import string from io import BytesIO +from typing import Optional, Union import discord import rapidfuzz @@ -75,8 +76,11 @@ class Colour(commands.Cog): await ctx.send(file=thumbnail_file, embed=colour_embed) @commands.group(aliases=("color",), invoke_without_command=True) - async def colour(self, ctx: commands.Context, *, color_input: str) -> None: + async def colour(self, ctx: commands.Context, *, color_input: Optional[str] = None) -> None: """User initiated command to create an embed that displays colour information.""" + if color_input is None: + await self.random() + if ctx.invoked_subcommand: return @@ -133,13 +137,17 @@ class Colour(commands.Cog): @colour.command() async def hex(self, ctx: commands.Context, hex_code: str) -> None: """Command to create an embed from a HEX input.""" - if "#" not in hex_code: + if hex_code[0] != "#": hex_code = f"#{hex_code}" - if len(hex_code) not in (4, 5, 7, 9) or any(_ not in string.hexdigits+"#" for _ in hex_code): - raise commands.BadArgument( - message="HEX values must be hexadecimal and take the form *#RRGGBB* or *#RGB*. " - f"User input was: `{hex_code}`." + + if len(hex_code) not in (4, 5, 7, 9) or any(_ not in string.hexdigits for _ in hex_code[1:]): + hex_error_embed = discord.Embed( + title="The input hex code is not valid.", + message=f"Cannot convert `{hex_code}` to a recognizable Hex format. " + "Hex values must be hexadecimal and take the form *#RRGGBB* or *#RGB*.", + colour=discord.Colour.dark_red() ) + await ctx.send(hex_error_embed) hex_tuple = ImageColor.getrgb(hex_code) if len(hex_tuple) == 4: @@ -149,7 +157,7 @@ class Colour(commands.Cog): @colour.command() async def name(self, ctx: commands.Context, *, user_colour_name: str) -> None: """Command to create an embed from a name input.""" - hex_colour = self.match_colour_name(user_colour_name) + hex_colour = await self.match_colour_name(ctx, user_colour_name) hex_tuple = ImageColor.getrgb(hex_colour) await self.send_colour_response(ctx, hex_tuple) @@ -224,7 +232,7 @@ class Colour(commands.Cog): colour_name = None return colour_name - def match_colour_name(self, input_colour_name: str) -> str: + async def match_colour_name(self, ctx: commands.Context, input_colour_name: str) -> Union[str, None]: """Convert a colour name to HEX code.""" try: match, certainty, _ = rapidfuzz.process.extractOne( @@ -233,7 +241,12 @@ class Colour(commands.Cog): score_cutoff=80 ) except (ValueError, TypeError): - raise commands.BadArgument(message=f"No color found for: `{input_colour_name}`") + name_error_embed = discord.Embed( + title="No colour match found.", + description=f"No color found for: `{input_colour_name}`", + colour=discord.Color.dark_red() + ) + await ctx.send(name_error_embed) return f"#{self.colour_mapping[match]}" -- cgit v1.2.3 From f2735273ceb7a2eabf3beee93e1ed21635b4bfd0 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 10 Nov 2021 08:40:46 -0500 Subject: test: return after default random invocation --- bot/exts/utilities/colour.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 259e3394..e0607bf5 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -80,6 +80,7 @@ class Colour(commands.Cog): """User initiated command to create an embed that displays colour information.""" if color_input is None: await self.random() + return if ctx.invoked_subcommand: return -- cgit v1.2.3 From 87711b7b90110fc9d62d1ef880f69c1fbe6366b6 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Wed, 10 Nov 2021 14:59:33 -0500 Subject: test: correct embed descriptions --- bot/exts/utilities/colour.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index e0607bf5..08a6973b 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -144,11 +144,11 @@ class Colour(commands.Cog): if len(hex_code) not in (4, 5, 7, 9) or any(_ not in string.hexdigits for _ in hex_code[1:]): hex_error_embed = discord.Embed( title="The input hex code is not valid.", - message=f"Cannot convert `{hex_code}` to a recognizable Hex format. " + description=f"Cannot convert `{hex_code}` to a recognizable Hex format. " "Hex values must be hexadecimal and take the form *#RRGGBB* or *#RGB*.", colour=discord.Colour.dark_red() ) - await ctx.send(hex_error_embed) + await ctx.send(embed=hex_error_embed) hex_tuple = ImageColor.getrgb(hex_code) if len(hex_tuple) == 4: @@ -247,7 +247,7 @@ class Colour(commands.Cog): description=f"No color found for: `{input_colour_name}`", colour=discord.Color.dark_red() ) - await ctx.send(name_error_embed) + await ctx.send(embed=name_error_embed) return f"#{self.colour_mapping[match]}" -- cgit v1.2.3 From aaad72a40b21b9d3b1d43d67a3206923722d8575 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Wed, 10 Nov 2021 19:08:58 -0500 Subject: cleanup: finalize reviews requested changes -Change _ to `digit` -Remove redundant "Command" from docstrings. Changed to "Create embed from ..." -Change hex command custom embed to `BadArgument` for consistency --- bot/exts/utilities/colour.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 08a6973b..9f7bedb5 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -79,7 +79,7 @@ class Colour(commands.Cog): async def colour(self, ctx: commands.Context, *, color_input: Optional[str] = None) -> None: """User initiated command to create an embed that displays colour information.""" if color_input is None: - await self.random() + await self.random(ctx) return if ctx.invoked_subcommand: @@ -93,7 +93,7 @@ class Colour(commands.Cog): @colour.command() async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None: - """Command to create an embed from an RGB input.""" + """Create an embed from an RGB input.""" if any(c not in range(256) for c in (red, green, blue)): raise commands.BadArgument( message=f"RGB values can only be from 0 to 255. User input was: `{red, green, blue}`." @@ -103,7 +103,7 @@ class Colour(commands.Cog): @colour.command() async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None: - """Command to create an embed from an HSV input.""" + """Create an embed from an HSV input.""" if (hue not in range(361)) or any(c not in range(101) for c in (saturation, value)): raise commands.BadArgument( message="Hue can only be from 0 to 360. Saturation and Value can only be from 0 to 100. " @@ -114,7 +114,7 @@ class Colour(commands.Cog): @colour.command() async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None: - """Command to create an embed from an HSL input.""" + """Create an embed from an HSL input.""" if (hue not in range(361)) or any(c not in range(101) for c in (saturation, lightness)): raise commands.BadArgument( message="Hue can only be from 0 to 360. Saturation and Lightness can only be from 0 to 100. " @@ -125,7 +125,7 @@ class Colour(commands.Cog): @colour.command() async def cmyk(self, ctx: commands.Context, cyan: int, magenta: int, yellow: int, key: int) -> None: - """Command to create an embed from a CMYK input.""" + """Create an embed from a CMYK input.""" if any(c not in range(101) for c in (cyan, magenta, yellow, key)): raise commands.BadArgument( message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`." @@ -137,18 +137,15 @@ class Colour(commands.Cog): @colour.command() async def hex(self, ctx: commands.Context, hex_code: str) -> None: - """Command to create an embed from a HEX input.""" + """Create an embed from a HEX input.""" if hex_code[0] != "#": hex_code = f"#{hex_code}" - if len(hex_code) not in (4, 5, 7, 9) or any(_ not in string.hexdigits for _ in hex_code[1:]): - hex_error_embed = discord.Embed( - title="The input hex code is not valid.", - description=f"Cannot convert `{hex_code}` to a recognizable Hex format. " - "Hex values must be hexadecimal and take the form *#RRGGBB* or *#RGB*.", - colour=discord.Colour.dark_red() + if len(hex_code) not in (4, 5, 7, 9) or any(digit not in string.hexdigits for digit in hex_code[1:]): + raise commands.BadArgument( + message=f"Cannot convert `{hex_code}` to a recognizable Hex format. " + "Hex values must be hexadecimal and take the form *#RRGGBB* or *#RGB*." ) - await ctx.send(embed=hex_error_embed) hex_tuple = ImageColor.getrgb(hex_code) if len(hex_tuple) == 4: @@ -157,14 +154,14 @@ class Colour(commands.Cog): @colour.command() async def name(self, ctx: commands.Context, *, user_colour_name: str) -> None: - """Command to create an embed from a name input.""" + """Create an embed from a name input.""" hex_colour = await self.match_colour_name(ctx, user_colour_name) hex_tuple = ImageColor.getrgb(hex_colour) await self.send_colour_response(ctx, hex_tuple) @colour.command() async def random(self, ctx: commands.Context) -> None: - """Command to create an embed from a randomly chosen colour from the reference file.""" + """Create an embed from a randomly chosen colour from the reference file.""" hex_colour = random.choice(list(self.colour_mapping.values())) hex_tuple = ImageColor.getrgb(f"#{hex_colour}") await self.send_colour_response(ctx, hex_tuple) -- cgit v1.2.3 From 231ad0e58ee7b2eb58f48833abe469bf61582bdc Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Wed, 10 Nov 2021 19:20:56 -0500 Subject: cleanup: change main command docstring to be more clear --- bot/exts/utilities/colour.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 9f7bedb5..b233fc1e 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -77,7 +77,11 @@ class Colour(commands.Cog): @commands.group(aliases=("color",), invoke_without_command=True) async def colour(self, ctx: commands.Context, *, color_input: Optional[str] = None) -> None: - """User initiated command to create an embed that displays colour information.""" + """ + Create an embed that displays colour information. + + If no subcommand is called, a randomly selected colour will be selected and shown. + """ if color_input is None: await self.random(ctx) return -- cgit v1.2.3 From ed1fb463c4e04fa4628e46df33144a4b5c579b82 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 11 Nov 2021 16:54:10 -0500 Subject: fix: remove async call for match_colour_name --- bot/exts/utilities/colour.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index b233fc1e..ed69beaf 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -159,7 +159,15 @@ class Colour(commands.Cog): @colour.command() async def name(self, ctx: commands.Context, *, user_colour_name: str) -> None: """Create an embed from a name input.""" - hex_colour = await self.match_colour_name(ctx, user_colour_name) + hex_colour = self.match_colour_name(ctx, user_colour_name) + if hex_colour is None: + name_error_embed = discord.Embed( + title="No colour match found.", + description=f"No color found for: `{user_colour_name}`", + colour=discord.Color.dark_red() + ) + await ctx.send(embed=name_error_embed) + return hex_tuple = ImageColor.getrgb(hex_colour) await self.send_colour_response(ctx, hex_tuple) @@ -234,7 +242,7 @@ class Colour(commands.Cog): colour_name = None return colour_name - async def match_colour_name(self, ctx: commands.Context, input_colour_name: str) -> Union[str, None]: + def match_colour_name(self, ctx: commands.Context, input_colour_name: str) -> Union[str, None]: """Convert a colour name to HEX code.""" try: match, certainty, _ = rapidfuzz.process.extractOne( @@ -243,12 +251,7 @@ class Colour(commands.Cog): score_cutoff=80 ) except (ValueError, TypeError): - name_error_embed = discord.Embed( - title="No colour match found.", - description=f"No color found for: `{input_colour_name}`", - colour=discord.Color.dark_red() - ) - await ctx.send(embed=name_error_embed) + return None return f"#{self.colour_mapping[match]}" -- cgit v1.2.3 From a8aebd4fe051052f52b68c822d0762c56eba9591 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 11 Nov 2021 16:58:09 -0500 Subject: fix: change color to Colour in comment --- bot/exts/utilities/colour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index ed69beaf..a35b393d 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -153,7 +153,7 @@ class Colour(commands.Cog): hex_tuple = ImageColor.getrgb(hex_code) if len(hex_tuple) == 4: - hex_tuple = hex_tuple[:-1] # color must be RGB. If RGBA, we remove the alpha value + hex_tuple = hex_tuple[:-1] # Colour must be RGB. If RGBA, we remove the alpha value await self.send_colour_response(ctx, hex_tuple) @colour.command() -- cgit v1.2.3 From 36a316f8ca27b35325ab31d575cc944a72f22c01 Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Thu, 11 Nov 2021 17:00:05 -0500 Subject: update: remove redundancy in docstring Co-authored-by: Hedy Li --- bot/exts/utilities/colour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index ed69beaf..3b5c8681 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -80,7 +80,7 @@ class Colour(commands.Cog): """ Create an embed that displays colour information. - If no subcommand is called, a randomly selected colour will be selected and shown. + If no subcommand is called, a randomly selected colour will be shown. """ if color_input is None: await self.random(ctx) -- cgit v1.2.3 From 1914905bdc7bd591ff906562ee0496346d37a045 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 11 Nov 2021 17:02:45 -0500 Subject: fix: remove alpha values in embed for hex --- bot/exts/utilities/colour.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index c865859a..d438fa27 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -43,7 +43,7 @@ class Colour(commands.Cog): elif colour_mode == "name": input_colour = ctx.kwargs["user_colour_name"] elif colour_mode == "hex": - input_colour = ctx.args[2:][0] + input_colour = ctx.args[2:][0][0:-2] else: input_colour = tuple(ctx.args[2:]) @@ -80,7 +80,7 @@ class Colour(commands.Cog): """ Create an embed that displays colour information. - If no subcommand is called, a randomly selected colour will be shown. + If no subcommand is called, a randomly selected colour will be selected and shown. """ if color_input is None: await self.random(ctx) -- cgit v1.2.3 From 6b3ac76a7e5e63503bbba139538e52d23480b3fc Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Thu, 11 Nov 2021 17:07:15 -0500 Subject: update: remove redundancy in dosctring --- bot/exts/utilities/colour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index d438fa27..6772fa1f 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -80,7 +80,7 @@ class Colour(commands.Cog): """ Create an embed that displays colour information. - If no subcommand is called, a randomly selected colour will be selected and shown. + If no subcommand is called, a randomly selected colour will be shown. """ if color_input is None: await self.random(ctx) -- cgit v1.2.3 From 45ac010fd6d2f8f9774cc5f408aa31c40fac42e6 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Fri, 12 Nov 2021 10:05:15 -0500 Subject: fix: check length of hex before strip --- bot/exts/utilities/colour.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 6772fa1f..66df5e0b 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -43,7 +43,9 @@ class Colour(commands.Cog): elif colour_mode == "name": input_colour = ctx.kwargs["user_colour_name"] elif colour_mode == "hex": - input_colour = ctx.args[2:][0][0:-2] + input_colour = ctx.args[2:][0] + if len(input_colour) >= 7: + input_colour = input_colour[0:-2] else: input_colour = tuple(ctx.args[2:]) -- cgit v1.2.3 From 5b3a23fe7b81d07c842271dc1947aa60133a2f8d Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Fri, 12 Nov 2021 10:08:21 -0500 Subject: fix: replace Union with Optional type hint Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/utilities/colour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 66df5e0b..5cd01fb5 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -244,7 +244,7 @@ class Colour(commands.Cog): colour_name = None return colour_name - def match_colour_name(self, ctx: commands.Context, input_colour_name: str) -> Union[str, None]: + def match_colour_name(self, ctx: commands.Context, input_colour_name: str) -> Optional[str]: """Convert a colour name to HEX code.""" try: match, certainty, _ = rapidfuzz.process.extractOne( -- cgit v1.2.3 From 8bbef57ccda68c388cf3d4b4fbc90fedf0e8597e Mon Sep 17 00:00:00 2001 From: brad90four <42116429+brad90four@users.noreply.github.com> Date: Fri, 12 Nov 2021 10:08:42 -0500 Subject: fix: bare return instead of explicit None Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/utilities/colour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 5cd01fb5..c7edec0d 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -253,7 +253,7 @@ class Colour(commands.Cog): score_cutoff=80 ) except (ValueError, TypeError): - return None + return return f"#{self.colour_mapping[match]}" -- cgit v1.2.3 From 6d5505366313b2ddb9d1a8f11b758e043e466ece Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Fri, 12 Nov 2021 10:10:12 -0500 Subject: fix: remove equal sign from hex length check --- bot/exts/utilities/colour.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index c7edec0d..66dbfa30 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -4,7 +4,7 @@ import pathlib import random import string from io import BytesIO -from typing import Optional, Union +from typing import Optional import discord import rapidfuzz @@ -44,7 +44,7 @@ class Colour(commands.Cog): input_colour = ctx.kwargs["user_colour_name"] elif colour_mode == "hex": input_colour = ctx.args[2:][0] - if len(input_colour) >= 7: + if len(input_colour) > 7: input_colour = input_colour[0:-2] else: input_colour = tuple(ctx.args[2:]) -- cgit v1.2.3 From e7923ed258ed7bf51cafbeb030df265a0992b1f1 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sat, 13 Nov 2021 09:44:03 -0500 Subject: fix: remove unnecessary return in main command --- bot/exts/utilities/colour.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 66dbfa30..0681e807 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -88,9 +88,6 @@ class Colour(commands.Cog): await self.random(ctx) return - if ctx.invoked_subcommand: - return - try: extra_colour = ImageColor.getrgb(color_input) await self.send_colour_response(ctx, extra_colour) -- cgit v1.2.3 From e87e037e772fa2319c786210f176fd5088f00131 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sat, 13 Nov 2021 10:09:04 -0500 Subject: fix: update type hint, color to colour, embed wording --- bot/exts/utilities/colour.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 0681e807..e33f65c6 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -29,7 +29,6 @@ class Colour(commands.Cog): async def send_colour_response(self, ctx: commands.Context, rgb: tuple[int, int, int]) -> None: """Create and send embed from user given colour information.""" name = self._rgb_to_name(rgb) - try: colour_or_color = ctx.invoked_parents[0] except IndexError: @@ -38,8 +37,9 @@ class Colour(commands.Cog): colour_mode = ctx.invoked_with if colour_mode == "random": colour_mode = colour_or_color + input_colour = name elif colour_mode in ("colour", "color"): - input_colour = rgb + input_colour = name elif colour_mode == "name": input_colour = ctx.kwargs["user_colour_name"] elif colour_mode == "hex": @@ -55,8 +55,8 @@ class Colour(commands.Cog): colour_mode = colour_mode.title() colour_embed = discord.Embed( - title=colour_or_color.title(), - description=f"{colour_mode} information for `{name or input_colour}`.", + title=f"{name or input_colour}", + description=f"{colour_or_color.title()} information for {colour_mode} `{input_colour or name}`.", colour=discord.Color.from_rgb(*rgb) ) colour_conversions = self.get_colour_conversions(rgb) @@ -78,18 +78,18 @@ class Colour(commands.Cog): await ctx.send(file=thumbnail_file, embed=colour_embed) @commands.group(aliases=("color",), invoke_without_command=True) - async def colour(self, ctx: commands.Context, *, color_input: Optional[str] = None) -> None: + async def colour(self, ctx: commands.Context, *, colour_input: Optional[str] = None) -> None: """ Create an embed that displays colour information. If no subcommand is called, a randomly selected colour will be shown. """ - if color_input is None: + if colour_input is None: await self.random(ctx) return try: - extra_colour = ImageColor.getrgb(color_input) + extra_colour = ImageColor.getrgb(colour_input) await self.send_colour_response(ctx, extra_colour) except ValueError: await invoke_help_command(ctx) @@ -162,7 +162,7 @@ class Colour(commands.Cog): if hex_colour is None: name_error_embed = discord.Embed( title="No colour match found.", - description=f"No color found for: `{user_colour_name}`", + description=f"No colour found for: `{user_colour_name}`", colour=discord.Color.dark_red() ) await ctx.send(embed=name_error_embed) @@ -172,7 +172,7 @@ class Colour(commands.Cog): @colour.command() async def random(self, ctx: commands.Context) -> None: - """Create an embed from a randomly chosen colour from the reference file.""" + """Create an embed from a randomly chosen colour.""" hex_colour = random.choice(list(self.colour_mapping.values())) hex_tuple = ImageColor.getrgb(f"#{hex_colour}") await self.send_colour_response(ctx, hex_tuple) @@ -227,7 +227,7 @@ class Colour(commands.Cog): hex_code = f"#{hex_}".upper() return hex_code - def _rgb_to_name(self, rgb: tuple[int, int, int]) -> str: + def _rgb_to_name(self, rgb: tuple[int, int, int]) -> Optional[str]: """Convert RGB values to a fuzzy matched name.""" input_hex_colour = self._rgb_to_hex(rgb) try: -- cgit v1.2.3 From e9cda06c2ab147ace8e64a27503cf99e17948418 Mon Sep 17 00:00:00 2001 From: bradtimmis Date: Sat, 13 Nov 2021 10:17:00 -0500 Subject: fix: handle bare command embed title --- bot/exts/utilities/colour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index e33f65c6..7c83fc66 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -39,7 +39,7 @@ class Colour(commands.Cog): colour_mode = colour_or_color input_colour = name elif colour_mode in ("colour", "color"): - input_colour = name + input_colour = ctx.kwargs["colour_input"] elif colour_mode == "name": input_colour = ctx.kwargs["user_colour_name"] elif colour_mode == "hex": -- cgit v1.2.3 From 4ec414ad37c6c10218b80ebd1b028840c0f0f853 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Tue, 29 Jun 2021 11:47:30 -0400 Subject: chore: Merge the .issue command into the github cog --- bot/exts/utilities/githubinfo.py | 273 +++++++++++++++++++++++++++++++++++++- bot/exts/utilities/issues.py | 277 --------------------------------------- 2 files changed, 267 insertions(+), 283 deletions(-) delete mode 100644 bot/exts/utilities/issues.py (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/githubinfo.py b/bot/exts/utilities/githubinfo.py index 539e388b..f0820731 100644 --- a/bot/exts/utilities/githubinfo.py +++ b/bot/exts/utilities/githubinfo.py @@ -1,5 +1,8 @@ import logging import random +import re +import typing as t +from dataclasses import dataclass from datetime import datetime from urllib.parse import quote, quote_plus @@ -7,24 +10,183 @@ import discord from discord.ext import commands from bot.bot import Bot -from bot.constants import Colours, NEGATIVE_REPLIES +from bot.constants import ( + Categories, + Channels, + Colours, + ERROR_REPLIES, + Emojis, + NEGATIVE_REPLIES, + Tokens, + WHITELISTED_CHANNELS +) from bot.exts.core.extensions import invoke_help_command +from bot.utils.decorators import whitelist_override log = logging.getLogger(__name__) GITHUB_API_URL = "https://api.github.com" +BAD_RESPONSE = { + 404: "Issue/pull request not located! Please enter a valid number!", + 403: "Rate limit has been hit! Please try again later!" +} +REQUEST_HEADERS = { + "Accept": "application/vnd.github.v3+json" +} + +REPOSITORY_ENDPOINT = "https://api.github.com/orgs/{org}/repos?per_page=100&type=public" +ISSUE_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/issues/{number}" +PR_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/pulls/{number}" + +if GITHUB_TOKEN := Tokens.github: + REQUEST_HEADERS["Authorization"] = f"token {GITHUB_TOKEN}" + +WHITELISTED_CATEGORIES = ( + Categories.development, Categories.devprojects, Categories.media, Categories.staff +) + +CODE_BLOCK_RE = re.compile( + r"^`([^`\n]+)`" # Inline codeblock + r"|```(.+?)```", # Multiline codeblock + re.DOTALL | re.MULTILINE +) + +# Maximum number of issues in one message +MAXIMUM_ISSUES = 5 + +# Regex used when looking for automatic linking in messages +# regex101 of current regex https://regex101.com/r/V2ji8M/6 +AUTOMATIC_REGEX = re.compile( + r"((?P[a-zA-Z0-9][a-zA-Z0-9\-]{1,39})\/)?(?P[\w\-\.]{1,100})#(?P[0-9]+)" +) + + +@dataclass +class FoundIssue: + """Dataclass representing an issue found by the regex.""" + + organisation: t.Optional[str] + repository: str + number: str + + def __hash__(self) -> int: + return hash((self.organisation, self.repository, self.number)) + + +@dataclass +class FetchError: + """Dataclass representing an error while fetching an issue.""" + + return_code: int + message: str + + +@dataclass +class IssueState: + """Dataclass representing the state of an issue.""" + + repository: str + number: int + url: str + title: str + emoji: str + class GithubInfo(commands.Cog): - """Fetches info from GitHub.""" + """A Cog that fetches info from GitHub.""" def __init__(self, bot: Bot): self.bot = bot + self.repos = [] + + @staticmethod + def remove_codeblocks(message: str) -> str: + """Remove any codeblock in a message.""" + return CODE_BLOCK_RE.sub("", message) + + async def fetch_issues( + self, + number: int, + repository: str, + user: str + ) -> t.Union[IssueState, FetchError]: + """ + Retrieve an issue from a GitHub repository. - async def fetch_data(self, url: str) -> dict: - """Retrieve data as a dictionary.""" - async with self.bot.http_session.get(url) as r: - return await r.json() + Returns IssueState on success, FetchError on failure. + """ + url = ISSUE_ENDPOINT.format(user=user, repository=repository, number=number) + pulls_url = PR_ENDPOINT.format(user=user, repository=repository, number=number) + log.trace(f"Querying GH issues API: {url}") + + async with self.bot.http_session.get(url, headers=REQUEST_HEADERS) as r: + json_data = await r.json() + + if r.status == 403: + if r.headers.get("X-RateLimit-Remaining") == "0": + log.info(f"Ratelimit reached while fetching {url}") + return FetchError(403, "Ratelimit reached, please retry in a few minutes.") + return FetchError(403, "Cannot access issue.") + elif r.status in (404, 410): + return FetchError(r.status, "Issue not found.") + elif r.status != 200: + return FetchError(r.status, "Error while fetching issue.") + + # The initial API request is made to the issues API endpoint, which will return information + # if the issue or PR is present. However, the scope of information returned for PRs differs + # from issues: if the 'issues' key is present in the response then we can pull the data we + # need from the initial API call. + if "issues" in json_data["html_url"]: + if json_data.get("state") == "open": + emoji = Emojis.issue_open + else: + emoji = Emojis.issue_closed + + # If the 'issues' key is not contained in the API response and there is no error code, then + # we know that a PR has been requested and a call to the pulls API endpoint is necessary + # to get the desired information for the PR. + else: + log.trace(f"PR provided, querying GH pulls API for additional information: {pulls_url}") + async with self.bot.http_session.get(pulls_url) as p: + pull_data = await p.json() + if pull_data["draft"]: + emoji = Emojis.pull_request_draft + elif pull_data["state"] == "open": + emoji = Emojis.pull_request_open + # When 'merged_at' is not None, this means that the state of the PR is merged + elif pull_data["merged_at"] is not None: + emoji = Emojis.pull_request_merged + else: + emoji = Emojis.pull_request_closed + + issue_url = json_data.get("html_url") + + return IssueState(repository, number, issue_url, json_data.get("title", ""), emoji) + + @staticmethod + def format_embed( + results: t.List[t.Union[IssueState, FetchError]], + user: str, + repository: t.Optional[str] = None + ) -> discord.Embed: + """Take a list of IssueState or FetchError and format a Discord embed for them.""" + description_list = [] + + for result in results: + if isinstance(result, IssueState): + description_list.append(f"{result.emoji} [{result.title}]({result.url})") + elif isinstance(result, FetchError): + description_list.append(f":x: [{result.return_code}] {result.message}") + + resp = discord.Embed( + colour=Colours.bright_green, + description="\n".join(description_list) + ) + + embed_url = f"https://github.com/{user}/{repository}" if repository else f"https://github.com/{user}" + resp.set_author(name="GitHub", url=embed_url) + return resp @commands.group(name="github", aliases=("gh", "git")) @commands.cooldown(1, 10, commands.BucketType.user) @@ -33,6 +195,105 @@ class GithubInfo(commands.Cog): if ctx.invoked_subcommand is None: await invoke_help_command(ctx) + @whitelist_override(channels=WHITELISTED_CHANNELS, categories=WHITELISTED_CATEGORIES) + @github_group.command(aliases=("pr", "issues", "prs"), root_aliases=("issue", "pr")) + async def issue( + self, + ctx: commands.Context, + numbers: commands.Greedy[int], + repository: str = "sir-lancebot", + user: str = "python-discord" + ) -> None: + """Command to retrieve issue(s) from a GitHub repository.""" + # Remove duplicates + numbers = set(numbers) + + err_message = None + if not numbers: + err_message = "You must have at least one issue/PR!" + + elif len(numbers) > MAXIMUM_ISSUES: + err_message = f"Too many issues/PRs! (maximum of {MAXIMUM_ISSUES})" + + # If there's an error with command invocation then send an error embed + if err_message is not None: + err_embed = discord.Embed( + title=random.choice(ERROR_REPLIES), + color=Colours.soft_red, + description=err_message + ) + await ctx.send(embed=err_embed) + await invoke_help_command(ctx) + return + + results = [await self.fetch_issues(number, repository, user) for number in numbers] + await ctx.send(embed=self.format_embed(results, user, repository)) + + @commands.Cog.listener() + async def on_message(self, message: discord.Message) -> None: + """ + Automatic issue linking. + + Listener to retrieve issue(s) from a GitHub repository using automatic linking if matching /#. + """ + # Ignore bots + if message.author.bot: + return + + issues = [ + FoundIssue(*match.group("org", "repo", "number")) + for match in AUTOMATIC_REGEX.finditer(self.remove_codeblocks(message.content)) + ] + links = [] + + if issues: + # Block this from working in DMs + if not message.guild: + await message.channel.send( + embed=discord.Embed( + title=random.choice(NEGATIVE_REPLIES), + description=( + "You can't retrieve issues from DMs. " + f"Try again in <#{Channels.community_bot_commands}>" + ), + colour=Colours.soft_red + ) + ) + return + + log.trace(f"Found {issues = }") + # Remove duplicates + issues = set(issues) + + if len(issues) > MAXIMUM_ISSUES: + embed = discord.Embed( + title=random.choice(ERROR_REPLIES), + color=Colours.soft_red, + description=f"Too many issues/PRs! (maximum of {MAXIMUM_ISSUES})" + ) + await message.channel.send(embed=embed, delete_after=5) + return + + for repo_issue in issues: + result = await self.fetch_issues( + int(repo_issue.number), + repo_issue.repository, + repo_issue.organisation or "python-discord" + ) + if isinstance(result, IssueState): + links.append(result) + + if not links: + return + + resp = self.format_embed(links, "python-discord") + await message.channel.send(embed=resp) + + async def fetch_data(self, url: str) -> dict: + """Retrieve data as a dictionary.""" + async with self.bot.http_session.get(url) as r: + return await r.json() + @github_group.command(name="user", aliases=("userinfo",)) async def github_user_info(self, ctx: commands.Context, username: str) -> None: """Fetches a user's GitHub information.""" diff --git a/bot/exts/utilities/issues.py b/bot/exts/utilities/issues.py deleted file mode 100644 index b6d5a43e..00000000 --- a/bot/exts/utilities/issues.py +++ /dev/null @@ -1,277 +0,0 @@ -import logging -import random -import re -from dataclasses import dataclass -from typing import Optional, Union - -import discord -from discord.ext import commands - -from bot.bot import Bot -from bot.constants import ( - Categories, Channels, Colours, ERROR_REPLIES, Emojis, NEGATIVE_REPLIES, Tokens, WHITELISTED_CHANNELS -) -from bot.utils.decorators import whitelist_override -from bot.utils.extensions import invoke_help_command - -log = logging.getLogger(__name__) - -BAD_RESPONSE = { - 404: "Issue/pull request not located! Please enter a valid number!", - 403: "Rate limit has been hit! Please try again later!" -} -REQUEST_HEADERS = { - "Accept": "application/vnd.github.v3+json" -} - -REPOSITORY_ENDPOINT = "https://api.github.com/orgs/{org}/repos?per_page=100&type=public" -ISSUE_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/issues/{number}" -PR_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/pulls/{number}" - -if GITHUB_TOKEN := Tokens.github: - REQUEST_HEADERS["Authorization"] = f"token {GITHUB_TOKEN}" - -WHITELISTED_CATEGORIES = ( - Categories.development, Categories.devprojects, Categories.media, Categories.staff -) - -CODE_BLOCK_RE = re.compile( - r"^`([^`\n]+)`" # Inline codeblock - r"|```(.+?)```", # Multiline codeblock - re.DOTALL | re.MULTILINE -) - -# Maximum number of issues in one message -MAXIMUM_ISSUES = 5 - -# Regex used when looking for automatic linking in messages -# regex101 of current regex https://regex101.com/r/V2ji8M/6 -AUTOMATIC_REGEX = re.compile( - r"((?P[a-zA-Z0-9][a-zA-Z0-9\-]{1,39})\/)?(?P[\w\-\.]{1,100})#(?P[0-9]+)" -) - - -@dataclass -class FoundIssue: - """Dataclass representing an issue found by the regex.""" - - organisation: Optional[str] - repository: str - number: str - - def __hash__(self) -> int: - return hash((self.organisation, self.repository, self.number)) - - -@dataclass -class FetchError: - """Dataclass representing an error while fetching an issue.""" - - return_code: int - message: str - - -@dataclass -class IssueState: - """Dataclass representing the state of an issue.""" - - repository: str - number: int - url: str - title: str - emoji: str - - -class Issues(commands.Cog): - """Cog that allows users to retrieve issues from GitHub.""" - - def __init__(self, bot: Bot): - self.bot = bot - self.repos = [] - - @staticmethod - def remove_codeblocks(message: str) -> str: - """Remove any codeblock in a message.""" - return re.sub(CODE_BLOCK_RE, "", message) - - async def fetch_issues( - self, - number: int, - repository: str, - user: str - ) -> Union[IssueState, FetchError]: - """ - Retrieve an issue from a GitHub repository. - - Returns IssueState on success, FetchError on failure. - """ - url = ISSUE_ENDPOINT.format(user=user, repository=repository, number=number) - pulls_url = PR_ENDPOINT.format(user=user, repository=repository, number=number) - log.trace(f"Querying GH issues API: {url}") - - async with self.bot.http_session.get(url, headers=REQUEST_HEADERS) as r: - json_data = await r.json() - - if r.status == 403: - if r.headers.get("X-RateLimit-Remaining") == "0": - log.info(f"Ratelimit reached while fetching {url}") - return FetchError(403, "Ratelimit reached, please retry in a few minutes.") - return FetchError(403, "Cannot access issue.") - elif r.status in (404, 410): - return FetchError(r.status, "Issue not found.") - elif r.status != 200: - return FetchError(r.status, "Error while fetching issue.") - - # The initial API request is made to the issues API endpoint, which will return information - # if the issue or PR is present. However, the scope of information returned for PRs differs - # from issues: if the 'issues' key is present in the response then we can pull the data we - # need from the initial API call. - if "issues" in json_data["html_url"]: - if json_data.get("state") == "open": - emoji = Emojis.issue_open - else: - emoji = Emojis.issue_closed - - # If the 'issues' key is not contained in the API response and there is no error code, then - # we know that a PR has been requested and a call to the pulls API endpoint is necessary - # to get the desired information for the PR. - else: - log.trace(f"PR provided, querying GH pulls API for additional information: {pulls_url}") - async with self.bot.http_session.get(pulls_url) as p: - pull_data = await p.json() - if pull_data["draft"]: - emoji = Emojis.pull_request_draft - elif pull_data["state"] == "open": - emoji = Emojis.pull_request_open - # When 'merged_at' is not None, this means that the state of the PR is merged - elif pull_data["merged_at"] is not None: - emoji = Emojis.pull_request_merged - else: - emoji = Emojis.pull_request_closed - - issue_url = json_data.get("html_url") - - return IssueState(repository, number, issue_url, json_data.get("title", ""), emoji) - - @staticmethod - def format_embed( - results: list[Union[IssueState, FetchError]], - user: str, - repository: Optional[str] = None - ) -> discord.Embed: - """Take a list of IssueState or FetchError and format a Discord embed for them.""" - description_list = [] - - for result in results: - if isinstance(result, IssueState): - description_list.append(f"{result.emoji} [{result.title}]({result.url})") - elif isinstance(result, FetchError): - description_list.append(f":x: [{result.return_code}] {result.message}") - - resp = discord.Embed( - colour=Colours.bright_green, - description="\n".join(description_list) - ) - - embed_url = f"https://github.com/{user}/{repository}" if repository else f"https://github.com/{user}" - resp.set_author(name="GitHub", url=embed_url) - return resp - - @whitelist_override(channels=WHITELISTED_CHANNELS, categories=WHITELISTED_CATEGORIES) - @commands.command(aliases=("issues", "pr", "prs")) - async def issue( - self, - ctx: commands.Context, - numbers: commands.Greedy[int], - repository: str = "sir-lancebot", - user: str = "python-discord" - ) -> None: - """Command to retrieve issue(s) from a GitHub repository.""" - # Remove duplicates - numbers = set(numbers) - - err_message = None - if not numbers: - err_message = "You must have at least one issue/PR!" - - elif len(numbers) > MAXIMUM_ISSUES: - err_message = f"Too many issues/PRs! (maximum of {MAXIMUM_ISSUES})" - - # If there's an error with command invocation then send an error embed - if err_message is not None: - err_embed = discord.Embed( - title=random.choice(ERROR_REPLIES), - color=Colours.soft_red, - description=err_message - ) - await ctx.send(embed=err_embed) - await invoke_help_command(ctx) - return - - results = [await self.fetch_issues(number, repository, user) for number in numbers] - await ctx.send(embed=self.format_embed(results, user, repository)) - - @commands.Cog.listener() - async def on_message(self, message: discord.Message) -> None: - """ - Automatic issue linking. - - Listener to retrieve issue(s) from a GitHub repository using automatic linking if matching /#. - """ - # Ignore bots - if message.author.bot: - return - - issues = [ - FoundIssue(*match.group("org", "repo", "number")) - for match in AUTOMATIC_REGEX.finditer(self.remove_codeblocks(message.content)) - ] - links = [] - - if issues: - # Block this from working in DMs - if not message.guild: - await message.channel.send( - embed=discord.Embed( - title=random.choice(NEGATIVE_REPLIES), - description=( - "You can't retrieve issues from DMs. " - f"Try again in <#{Channels.community_bot_commands}>" - ), - colour=Colours.soft_red - ) - ) - return - - log.trace(f"Found {issues = }") - # Remove duplicates - issues = set(issues) - - if len(issues) > MAXIMUM_ISSUES: - embed = discord.Embed( - title=random.choice(ERROR_REPLIES), - color=Colours.soft_red, - description=f"Too many issues/PRs! (maximum of {MAXIMUM_ISSUES})" - ) - await message.channel.send(embed=embed, delete_after=5) - return - - for repo_issue in issues: - result = await self.fetch_issues( - int(repo_issue.number), - repo_issue.repository, - repo_issue.organisation or "python-discord" - ) - if isinstance(result, IssueState): - links.append(result) - - if not links: - return - - resp = self.format_embed(links, "python-discord") - await message.channel.send(embed=resp) - - -def setup(bot: Bot) -> None: - """Load the Issues cog.""" - bot.add_cog(Issues(bot)) -- cgit v1.2.3 From ba10b9b6525beac6637e5a13ead03fb018751201 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sat, 31 Jul 2021 18:42:36 -0400 Subject: chore: Remove the .issue command --- bot/exts/utilities/githubinfo.py | 37 ------------------------------------- 1 file changed, 37 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/githubinfo.py b/bot/exts/utilities/githubinfo.py index f0820731..b0b327b6 100644 --- a/bot/exts/utilities/githubinfo.py +++ b/bot/exts/utilities/githubinfo.py @@ -18,11 +18,8 @@ from bot.constants import ( Emojis, NEGATIVE_REPLIES, Tokens, - WHITELISTED_CHANNELS ) from bot.exts.core.extensions import invoke_help_command -from bot.utils.decorators import whitelist_override - log = logging.getLogger(__name__) GITHUB_API_URL = "https://api.github.com" @@ -195,40 +192,6 @@ class GithubInfo(commands.Cog): if ctx.invoked_subcommand is None: await invoke_help_command(ctx) - @whitelist_override(channels=WHITELISTED_CHANNELS, categories=WHITELISTED_CATEGORIES) - @github_group.command(aliases=("pr", "issues", "prs"), root_aliases=("issue", "pr")) - async def issue( - self, - ctx: commands.Context, - numbers: commands.Greedy[int], - repository: str = "sir-lancebot", - user: str = "python-discord" - ) -> None: - """Command to retrieve issue(s) from a GitHub repository.""" - # Remove duplicates - numbers = set(numbers) - - err_message = None - if not numbers: - err_message = "You must have at least one issue/PR!" - - elif len(numbers) > MAXIMUM_ISSUES: - err_message = f"Too many issues/PRs! (maximum of {MAXIMUM_ISSUES})" - - # If there's an error with command invocation then send an error embed - if err_message is not None: - err_embed = discord.Embed( - title=random.choice(ERROR_REPLIES), - color=Colours.soft_red, - description=err_message - ) - await ctx.send(embed=err_embed) - await invoke_help_command(ctx) - return - - results = [await self.fetch_issues(number, repository, user) for number in numbers] - await ctx.send(embed=self.format_embed(results, user, repository)) - @commands.Cog.listener() async def on_message(self, message: discord.Message) -> None: """ -- cgit v1.2.3 From 101001bfabae0cef37cf36ebbf4420f9d80736e4 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sat, 18 Sep 2021 10:28:35 -0400 Subject: chore: Apply suggested changes --- bot/exts/utilities/githubinfo.py | 90 ++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 55 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/githubinfo.py b/bot/exts/utilities/githubinfo.py index b0b327b6..c9a65668 100644 --- a/bot/exts/utilities/githubinfo.py +++ b/bot/exts/utilities/githubinfo.py @@ -4,15 +4,15 @@ import re import typing as t from dataclasses import dataclass from datetime import datetime -from urllib.parse import quote, quote_plus +from urllib.parse import quote import discord +from aiohttp import ClientResponse from discord.ext import commands from bot.bot import Bot from bot.constants import ( Categories, - Channels, Colours, ERROR_REPLIES, Emojis, @@ -20,14 +20,11 @@ from bot.constants import ( Tokens, ) from bot.exts.core.extensions import invoke_help_command + log = logging.getLogger(__name__) GITHUB_API_URL = "https://api.github.com" -BAD_RESPONSE = { - 404: "Issue/pull request not located! Please enter a valid number!", - 403: "Rate limit has been hit! Please try again later!" -} REQUEST_HEADERS = { "Accept": "application/vnd.github.v3+json" } @@ -102,11 +99,11 @@ class GithubInfo(commands.Cog): """Remove any codeblock in a message.""" return CODE_BLOCK_RE.sub("", message) - async def fetch_issues( - self, - number: int, - repository: str, - user: str + async def fetch_issue( + self, + number: int, + repository: str, + user: str ) -> t.Union[IssueState, FetchError]: """ Retrieve an issue from a GitHub repository. @@ -117,8 +114,7 @@ class GithubInfo(commands.Cog): pulls_url = PR_ENDPOINT.format(user=user, repository=repository, number=number) log.trace(f"Querying GH issues API: {url}") - async with self.bot.http_session.get(url, headers=REQUEST_HEADERS) as r: - json_data = await r.json() + json_data, r = await self.fetch_data(url) if r.status == 403: if r.headers.get("X-RateLimit-Remaining") == "0": @@ -145,17 +141,17 @@ class GithubInfo(commands.Cog): # to get the desired information for the PR. else: log.trace(f"PR provided, querying GH pulls API for additional information: {pulls_url}") - async with self.bot.http_session.get(pulls_url) as p: - pull_data = await p.json() - if pull_data["draft"]: - emoji = Emojis.pull_request_draft - elif pull_data["state"] == "open": - emoji = Emojis.pull_request_open - # When 'merged_at' is not None, this means that the state of the PR is merged - elif pull_data["merged_at"] is not None: - emoji = Emojis.pull_request_merged - else: - emoji = Emojis.pull_request_closed + + pull_data, _ = await self.fetch_data(pulls_url) + if pull_data["draft"]: + emoji = Emojis.pull_request_draft + elif pull_data["state"] == "open": + emoji = Emojis.pull_request_open + # When 'merged_at' is not None, this means that the state of the PR is merged + elif pull_data["merged_at"] is not None: + emoji = Emojis.pull_request_merged + else: + emoji = Emojis.pull_request_closed issue_url = json_data.get("html_url") @@ -163,9 +159,7 @@ class GithubInfo(commands.Cog): @staticmethod def format_embed( - results: t.List[t.Union[IssueState, FetchError]], - user: str, - repository: t.Optional[str] = None + results: t.List[t.Union[IssueState, FetchError]] ) -> discord.Embed: """Take a list of IssueState or FetchError and format a Discord embed for them.""" description_list = [] @@ -181,8 +175,7 @@ class GithubInfo(commands.Cog): description="\n".join(description_list) ) - embed_url = f"https://github.com/{user}/{repository}" if repository else f"https://github.com/{user}" - resp.set_author(name="GitHub", url=embed_url) + resp.set_author(name="GitHub") return resp @commands.group(name="github", aliases=("gh", "git")) @@ -212,16 +205,6 @@ class GithubInfo(commands.Cog): if issues: # Block this from working in DMs if not message.guild: - await message.channel.send( - embed=discord.Embed( - title=random.choice(NEGATIVE_REPLIES), - description=( - "You can't retrieve issues from DMs. " - f"Try again in <#{Channels.community_bot_commands}>" - ), - colour=Colours.soft_red - ) - ) return log.trace(f"Found {issues = }") @@ -238,7 +221,7 @@ class GithubInfo(commands.Cog): return for repo_issue in issues: - result = await self.fetch_issues( + result = await self.fetch_issue( int(repo_issue.number), repo_issue.repository, repo_issue.organisation or "python-discord" @@ -249,19 +232,19 @@ class GithubInfo(commands.Cog): if not links: return - resp = self.format_embed(links, "python-discord") + resp = self.format_embed(links) await message.channel.send(embed=resp) - async def fetch_data(self, url: str) -> dict: - """Retrieve data as a dictionary.""" - async with self.bot.http_session.get(url) as r: - return await r.json() + async def fetch_data(self, url: str) -> tuple[dict[str], ClientResponse]: + """Retrieve data as a dictionary and the response in a tuple.""" + async with self.bot.http_session.get(url, heades=REQUEST_HEADERS) as r: + return await r.json(), r @github_group.command(name="user", aliases=("userinfo",)) async def github_user_info(self, ctx: commands.Context, username: str) -> None: """Fetches a user's GitHub information.""" async with ctx.typing(): - user_data = await self.fetch_data(f"{GITHUB_API_URL}/users/{quote_plus(username)}") + user_data, _ = await self.fetch_data(f"{GITHUB_API_URL}/users/{username}") # User_data will not have a message key if the user exists if "message" in user_data: @@ -274,7 +257,7 @@ class GithubInfo(commands.Cog): await ctx.send(embed=embed) return - org_data = await self.fetch_data(user_data["organizations_url"]) + org_data, _ = await self.fetch_data(user_data["organizations_url"]) orgs = [f"[{org['login']}](https://github.com/{org['login']})" for org in org_data] orgs_to_add = " | ".join(orgs) @@ -290,8 +273,8 @@ class GithubInfo(commands.Cog): embed = discord.Embed( title=f"`{user_data['login']}`'s GitHub profile info", - description=f"```\n{user_data['bio']}\n```\n" if user_data["bio"] else "", - colour=discord.Colour.og_blurple(), + description=f"```{user_data['bio']}```\n" if user_data["bio"] else "", + colour=discord.Colour.blurple(), url=user_data["html_url"], timestamp=datetime.strptime(user_data["created_at"], "%Y-%m-%dT%H:%M:%SZ") ) @@ -315,10 +298,7 @@ class GithubInfo(commands.Cog): ) if user_data["type"] == "User": - embed.add_field( - name="Gists", - value=f"[{gists}](https://gist.github.com/{quote_plus(username, safe='')})" - ) + embed.add_field(name="Gists", value=f"[{gists}](https://gist.github.com/{quote(username, safe='')})") embed.add_field( name=f"Organization{'s' if len(orgs)!=1 else ''}", @@ -347,7 +327,7 @@ class GithubInfo(commands.Cog): return async with ctx.typing(): - repo_data = await self.fetch_data(f"{GITHUB_API_URL}/repos/{quote(repo)}") + repo_data, _ = await self.fetch_data(f"{GITHUB_API_URL}/repos/{quote(repo)}") # There won't be a message key if this repo exists if "message" in repo_data: @@ -363,7 +343,7 @@ class GithubInfo(commands.Cog): embed = discord.Embed( title=repo_data["name"], description=repo_data["description"], - colour=discord.Colour.og_blurple(), + colour=discord.Colour.blurple(), url=repo_data["html_url"] ) -- cgit v1.2.3 From cb4114823b91056d9b552d3e75c3c8ca9e879da7 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 2 Dec 2021 11:33:57 +0000 Subject: Make dataclasses hashable, and fix kwarg spelling error --- bot/exts/utilities/githubinfo.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/githubinfo.py b/bot/exts/utilities/githubinfo.py index c9a65668..ee05497a 100644 --- a/bot/exts/utilities/githubinfo.py +++ b/bot/exts/utilities/githubinfo.py @@ -11,14 +11,7 @@ from aiohttp import ClientResponse from discord.ext import commands from bot.bot import Bot -from bot.constants import ( - Categories, - Colours, - ERROR_REPLIES, - Emojis, - NEGATIVE_REPLIES, - Tokens, -) +from bot.constants import Categories, Colours, ERROR_REPLIES, Emojis, NEGATIVE_REPLIES, Tokens from bot.exts.core.extensions import invoke_help_command log = logging.getLogger(__name__) @@ -41,7 +34,7 @@ WHITELISTED_CATEGORIES = ( ) CODE_BLOCK_RE = re.compile( - r"^`([^`\n]+)`" # Inline codeblock + r"^`([^`\n]+)`" # Inline codeblock r"|```(.+?)```", # Multiline codeblock re.DOTALL | re.MULTILINE ) @@ -56,7 +49,7 @@ AUTOMATIC_REGEX = re.compile( ) -@dataclass +@dataclass(eq=True, frozen=True) class FoundIssue: """Dataclass representing an issue found by the regex.""" @@ -64,11 +57,8 @@ class FoundIssue: repository: str number: str - def __hash__(self) -> int: - return hash((self.organisation, self.repository, self.number)) - -@dataclass +@dataclass(eq=True, frozen=True) class FetchError: """Dataclass representing an error while fetching an issue.""" @@ -76,7 +66,7 @@ class FetchError: message: str -@dataclass +@dataclass(eq=True, frozen=True) class IssueState: """Dataclass representing the state of an issue.""" @@ -237,7 +227,7 @@ class GithubInfo(commands.Cog): async def fetch_data(self, url: str) -> tuple[dict[str], ClientResponse]: """Retrieve data as a dictionary and the response in a tuple.""" - async with self.bot.http_session.get(url, heades=REQUEST_HEADERS) as r: + async with self.bot.http_session.get(url, headers=REQUEST_HEADERS) as r: return await r.json(), r @github_group.command(name="user", aliases=("userinfo",)) -- cgit v1.2.3 From cdd4067ad7497b48440074494aa4de15a908f7d5 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 2 Dec 2021 11:42:04 +0000 Subject: use og_blurple in issue embed for consistency --- bot/exts/utilities/githubinfo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/githubinfo.py b/bot/exts/utilities/githubinfo.py index ee05497a..b7dbe64d 100644 --- a/bot/exts/utilities/githubinfo.py +++ b/bot/exts/utilities/githubinfo.py @@ -264,7 +264,7 @@ class GithubInfo(commands.Cog): embed = discord.Embed( title=f"`{user_data['login']}`'s GitHub profile info", description=f"```{user_data['bio']}```\n" if user_data["bio"] else "", - colour=discord.Colour.blurple(), + colour=discord.Colour.og_blurple(), url=user_data["html_url"], timestamp=datetime.strptime(user_data["created_at"], "%Y-%m-%dT%H:%M:%SZ") ) @@ -333,7 +333,7 @@ class GithubInfo(commands.Cog): embed = discord.Embed( title=repo_data["name"], description=repo_data["description"], - colour=discord.Colour.blurple(), + colour=discord.Colour.og_blurple(), url=repo_data["html_url"] ) -- cgit v1.2.3 From e584697e0809e60cda899b2148a1efcff993177a Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Fri, 3 Dec 2021 18:38:42 +0000 Subject: Move logging and remove unused varibales in GitHubInfo cog --- bot/exts/utilities/githubinfo.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/githubinfo.py b/bot/exts/utilities/githubinfo.py index b7dbe64d..009e0fad 100644 --- a/bot/exts/utilities/githubinfo.py +++ b/bot/exts/utilities/githubinfo.py @@ -11,7 +11,7 @@ from aiohttp import ClientResponse from discord.ext import commands from bot.bot import Bot -from bot.constants import Categories, Colours, ERROR_REPLIES, Emojis, NEGATIVE_REPLIES, Tokens +from bot.constants import Colours, ERROR_REPLIES, Emojis, NEGATIVE_REPLIES, Tokens from bot.exts.core.extensions import invoke_help_command log = logging.getLogger(__name__) @@ -26,12 +26,8 @@ REPOSITORY_ENDPOINT = "https://api.github.com/orgs/{org}/repos?per_page=100&type ISSUE_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/issues/{number}" PR_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/pulls/{number}" -if GITHUB_TOKEN := Tokens.github: - REQUEST_HEADERS["Authorization"] = f"token {GITHUB_TOKEN}" - -WHITELISTED_CATEGORIES = ( - Categories.development, Categories.devprojects, Categories.media, Categories.staff -) +if Tokens.github: + REQUEST_HEADERS["Authorization"] = f"token {Tokens.github}" CODE_BLOCK_RE = re.compile( r"^`([^`\n]+)`" # Inline codeblock @@ -102,7 +98,6 @@ class GithubInfo(commands.Cog): """ url = ISSUE_ENDPOINT.format(user=user, repository=repository, number=number) pulls_url = PR_ENDPOINT.format(user=user, repository=repository, number=number) - log.trace(f"Querying GH issues API: {url}") json_data, r = await self.fetch_data(url) @@ -130,8 +125,6 @@ class GithubInfo(commands.Cog): # we know that a PR has been requested and a call to the pulls API endpoint is necessary # to get the desired information for the PR. else: - log.trace(f"PR provided, querying GH pulls API for additional information: {pulls_url}") - pull_data, _ = await self.fetch_data(pulls_url) if pull_data["draft"]: emoji = Emojis.pull_request_draft @@ -227,6 +220,7 @@ class GithubInfo(commands.Cog): async def fetch_data(self, url: str) -> tuple[dict[str], ClientResponse]: """Retrieve data as a dictionary and the response in a tuple.""" + log.trace(f"Querying GH issues API: {url}") async with self.bot.http_session.get(url, headers=REQUEST_HEADERS) as r: return await r.json(), r -- cgit v1.2.3 From 598cf151cfaaa8e6777cd602714c10666ad45b4a Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 5 Dec 2021 13:12:41 +0000 Subject: Reflect new message converter behaviour in bm help message Since w epatched the message converter to work as intended, the help message given to a user when failing to resolve a message reference to a message object has been updated. --- bot/exts/utilities/bookmark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/bookmark.py b/bot/exts/utilities/bookmark.py index a11c366b..b50205a0 100644 --- a/bot/exts/utilities/bookmark.py +++ b/bot/exts/utilities/bookmark.py @@ -102,7 +102,7 @@ class Bookmark(commands.Cog): "You must either provide a valid message to bookmark, or reply to one." "\n\nThe lookup strategy for a message is as follows (in order):" "\n1. Lookup by '{channel ID}-{message ID}' (retrieved by shift-clicking on 'Copy ID')" - "\n2. Lookup by message ID (the message **must** have been sent after the bot last started)" + "\n2. Lookup by message ID (the message **must** be in the context channel)" "\n3. Lookup by message URL" ) target_message = ctx.message.reference.resolved -- cgit v1.2.3 From 55f008ee03c15b24551bdc5509459337df9acd8e Mon Sep 17 00:00:00 2001 From: aru Date: Wed, 15 Dec 2021 18:15:16 -0500 Subject: minor: allow color command in dev-media (#944) Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/utilities/colour.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py index 7c83fc66..ee6bad93 100644 --- a/bot/exts/utilities/colour.py +++ b/bot/exts/utilities/colour.py @@ -11,8 +11,10 @@ import rapidfuzz from PIL import Image, ImageColor from discord.ext import commands +from bot import constants from bot.bot import Bot from bot.exts.core.extensions import invoke_help_command +from bot.utils.decorators import whitelist_override THUMBNAIL_SIZE = (80, 80) @@ -78,6 +80,11 @@ class Colour(commands.Cog): await ctx.send(file=thumbnail_file, embed=colour_embed) @commands.group(aliases=("color",), invoke_without_command=True) + @whitelist_override( + channels=constants.WHITELISTED_CHANNELS, + roles=constants.STAFF_ROLES, + categories=[constants.Categories.development, constants.Categories.media] + ) async def colour(self, ctx: commands.Context, *, colour_input: Optional[str] = None) -> None: """ Create an embed that displays colour information. -- cgit v1.2.3 From 4d47cae15594babfb6e4c4a77b0fc13176e8f7bc Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Tue, 28 Dec 2021 20:10:17 +0000 Subject: Remove latex cog and matplotlib matplotlib, and its sub dependencies, caused a fresh install of an environment to take multiple minutes. As the latex cog is the only one that used it, and that is currently disabled, we have decided to remove it entirely. Git gives us the benefit of being able to see deleted files. So whoever decides to implement latex again can use that for reference. --- bot/exts/utilities/latex.py | 101 -------- poetry.lock | 579 +++++++++++++++++++------------------------- pyproject.toml | 1 - 3 files changed, 250 insertions(+), 431 deletions(-) delete mode 100644 bot/exts/utilities/latex.py (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/latex.py b/bot/exts/utilities/latex.py deleted file mode 100644 index 36c7e0ab..00000000 --- a/bot/exts/utilities/latex.py +++ /dev/null @@ -1,101 +0,0 @@ -import asyncio -import hashlib -import pathlib -import re -from concurrent.futures import ThreadPoolExecutor -from io import BytesIO - -import discord -import matplotlib.pyplot as plt -from discord.ext import commands - -from bot.bot import Bot - -# configure fonts and colors for matplotlib -plt.rcParams.update( - { - "font.size": 16, - "mathtext.fontset": "cm", # Computer Modern font set - "mathtext.rm": "serif", - "figure.facecolor": "36393F", # matches Discord's dark mode background color - "text.color": "white", - } -) - -FORMATTED_CODE_REGEX = re.compile( - r"(?P(?P```)|``?)" # code delimiter: 1-3 backticks; (?P=block) only matches if it's a block - r"(?(block)(?:(?P[a-z]+)\n)?)" # if we're in a block, match optional language (only letters plus newline) - r"(?:[ \t]*\n)*" # any blank (empty or tabs/spaces only) lines before the code - r"(?P.*?)" # extract all code inside the markup - r"\s*" # any more whitespace before the end of the code markup - r"(?P=delim)", # match the exact same delimiter from the start again - re.DOTALL | re.IGNORECASE, # "." also matches newlines, case insensitive -) - -CACHE_DIRECTORY = pathlib.Path("_latex_cache") -CACHE_DIRECTORY.mkdir(exist_ok=True) - - -class Latex(commands.Cog): - """Renders latex.""" - - @staticmethod - def _render(text: str, filepath: pathlib.Path) -> BytesIO: - """ - Return the rendered image if latex compiles without errors, otherwise raise a BadArgument Exception. - - Saves rendered image to cache. - """ - fig = plt.figure() - rendered_image = BytesIO() - fig.text(0, 1, text, horizontalalignment="left", verticalalignment="top") - - try: - plt.savefig(rendered_image, bbox_inches="tight", dpi=600) - except ValueError as e: - raise commands.BadArgument(str(e)) - - rendered_image.seek(0) - - with open(filepath, "wb") as f: - f.write(rendered_image.getbuffer()) - - return rendered_image - - @staticmethod - def _prepare_input(text: str) -> str: - text = text.replace(r"\\", "$\n$") # matplotlib uses \n for newlines, not \\ - - if match := FORMATTED_CODE_REGEX.match(text): - return match.group("code") - else: - return text - - @commands.command() - @commands.max_concurrency(1, commands.BucketType.guild, wait=True) - async def latex(self, ctx: commands.Context, *, text: str) -> None: - """Renders the text in latex and sends the image.""" - text = self._prepare_input(text) - query_hash = hashlib.md5(text.encode()).hexdigest() - image_path = CACHE_DIRECTORY.joinpath(f"{query_hash}.png") - async with ctx.typing(): - if image_path.exists(): - await ctx.send(file=discord.File(image_path)) - return - - with ThreadPoolExecutor() as pool: - image = await asyncio.get_running_loop().run_in_executor( - pool, self._render, text, image_path - ) - - await ctx.send(file=discord.File(image, "latex.png")) - - -def setup(bot: Bot) -> None: - """Load the Latex Cog.""" - # As we have resource issues on this cog, - # we have it currently disabled while we fix it. - import logging - logging.info("Latex cog is currently disabled. It won't be loaded.") - return - bot.add_cog(Latex()) diff --git a/poetry.lock b/poetry.lock index 5e950343..6a83efed 100644 --- a/poetry.lock +++ b/poetry.lock @@ -76,29 +76,17 @@ python-versions = ">=3.5.3" [[package]] name = "attrs" -version = "21.2.0" +version = "21.3.0" description = "Classes Without Boilerplate" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] - -[[package]] -name = "backports.entry-points-selectable" -version = "1.1.1" -description = "Compatibility shim providing selectable entry points for older implementations" -category = "dev" -optional = false -python-versions = ">=2.7" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] [[package]] name = "beautifulsoup4" @@ -173,12 +161,18 @@ humanfriendly = ">=9.1" cron = ["capturer (>=2.4)"] [[package]] -name = "cycler" -version = "0.11.0" -description = "Composable style cycles" +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] [[package]] name = "discord.py" @@ -199,10 +193,9 @@ voice = ["PyNaCl (>=1.3.0,<1.5)"] [package.source] type = "url" url = "https://github.com/Rapptz/discord.py/archive/45d498c1b76deaf3b394d17ccf56112fa691d160.zip" - [[package]] name = "distlib" -version = "0.3.3" +version = "0.3.4" description = "Distribution utilities" category = "dev" optional = false @@ -218,7 +211,7 @@ python-versions = "*" [[package]] name = "fakeredis" -version = "1.6.1" +version = "1.7.0" description = "Fake implementation of redis API for testing purposes." category = "main" optional = false @@ -226,7 +219,7 @@ python-versions = ">=3.5" [package.dependencies] packaging = "*" -redis = "<3.6.0" +redis = "<4.1.0" six = ">=1.12" sortedcontainers = "*" @@ -236,11 +229,11 @@ lua = ["lupa"] [[package]] name = "filelock" -version = "3.3.2" +version = "3.4.2" description = "A platform independent file lock." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] @@ -378,14 +371,14 @@ pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_ve [[package]] name = "identify" -version = "2.3.5" +version = "2.4.1" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.6.1" [package.extras] -license = ["editdistance-s"] +license = ["ukkonen"] [[package]] name = "idna" @@ -409,17 +402,9 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] plugins = ["setuptools"] -[[package]] -name = "kiwisolver" -version = "1.3.2" -description = "A fast implementation of the Cassowary constraint solver" -category = "main" -optional = false -python-versions = ">=3.7" - [[package]] name = "lxml" -version = "4.6.5" +version = "4.7.1" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." category = "main" optional = false @@ -431,22 +416,6 @@ html5 = ["html5lib"] htmlsoup = ["beautifulsoup4"] source = ["Cython (>=0.29.7)"] -[[package]] -name = "matplotlib" -version = "3.4.3" -description = "Python plotting package" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -cycler = ">=0.10" -kiwisolver = ">=1.0.1" -numpy = ">=1.16" -pillow = ">=6.2.0" -pyparsing = ">=2.2.1" -python-dateutil = ">=2.7" - [[package]] name = "mccabe" version = "0.6.1" @@ -479,24 +448,16 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "numpy" -version = "1.21.1" -description = "NumPy is the fundamental package for array computing with Python." -category = "main" -optional = false -python-versions = ">=3.7" - [[package]] name = "packaging" -version = "21.0" +version = "21.3" description = "Core utilities for Python packages" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pep8-naming" @@ -534,11 +495,11 @@ test = ["docutils", "pytest-cov", "pytest-pycodestyle", "pytest-runner"] [[package]] name = "platformdirs" -version = "2.4.0" +version = "2.4.1" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] @@ -546,7 +507,7 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock [[package]] name = "pre-commit" -version = "2.15.0" +version = "2.16.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -633,7 +594,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyparsing" -version = "3.0.5" +version = "3.0.6" description = "Python parsing module" category = "main" optional = false @@ -663,7 +624,7 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "0.19.1" +version = "0.19.2" description = "Read key-value pairs from a .env file and set them as environment variables" category = "dev" optional = false @@ -682,7 +643,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "rapidfuzz" -version = "1.8.2" +version = "1.9.1" description = "rapid fuzzy string matching" category = "main" optional = false @@ -693,14 +654,17 @@ full = ["numpy"] [[package]] name = "redis" -version = "3.5.3" -description = "Python client for Redis key-value store" +version = "4.0.2" +description = "Python client for Redis database and key-value store" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" + +[package.dependencies] +deprecated = "*" [package.extras] -hiredis = ["hiredis (>=0.1.3)"] +hiredis = ["hiredis (>=1.0.0)"] [[package]] name = "sentry-sdk" @@ -740,7 +704,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "snowballstemmer" -version = "2.1.0" +version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "dev" optional = false @@ -756,7 +720,7 @@ python-versions = "*" [[package]] name = "soupsieve" -version = "2.3" +version = "2.3.1" description = "A modern CSS selector implementation for Beautiful Soup." category = "main" optional = false @@ -799,11 +763,11 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "urllib3" @@ -820,14 +784,13 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.10.0" +version = "20.11.0" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -"backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" platformdirs = ">=2,<3" @@ -837,6 +800,14 @@ six = ">=1.9.0,<2" docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] +[[package]] +name = "wrapt" +version = "1.13.3" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + [[package]] name = "yarl" version = "1.7.2" @@ -852,7 +823,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "b091f392619f058d265309c9474d9d2b86f53f634017210cca11713cdb11ae14" +content-hash = "e3682fd5b518ada5066b36015210f00d223c5485c24e4e7c377e371fe6ef0a0d" [metadata.files] aiodns = [ @@ -915,12 +886,8 @@ async-timeout = [ {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, -] -"backports.entry-points-selectable" = [ - {file = "backports.entry_points_selectable-1.1.1-py2.py3-none-any.whl", hash = "sha256:7fceed9532a7aa2bd888654a7314f864a3c16a4e710b34a58cfc0f08114c663b"}, - {file = "backports.entry_points_selectable-1.1.1.tar.gz", hash = "sha256:914b21a479fde881635f7af5adc7f6e38d6b274be32269070c53b698c60d5386"}, + {file = "attrs-21.3.0-py2.py3-none-any.whl", hash = "sha256:8f7335278dedd26b58c38e006338242cc0977f06d51579b2b8b87b9b33bff66c"}, + {file = "attrs-21.3.0.tar.gz", hash = "sha256:50f3c9b216dc9021042f71b392859a773b904ce1a029077f58f6598272432045"}, ] beautifulsoup4 = [ {file = "beautifulsoup4-4.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"}, @@ -998,26 +965,26 @@ coloredlogs = [ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, ] -cycler = [ - {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, - {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, ] "discord.py" = [] distlib = [ - {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"}, - {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"}, + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] emojis = [ {file = "emojis-0.6.0-py3-none-any.whl", hash = "sha256:7da34c8a78ae262fd68cef9e2c78a3c1feb59784489eeea0f54ba1d4b7111c7c"}, {file = "emojis-0.6.0.tar.gz", hash = "sha256:bf605d1f1a27a81cd37fe82eb65781c904467f569295a541c33710b97e4225ec"}, ] fakeredis = [ - {file = "fakeredis-1.6.1-py3-none-any.whl", hash = "sha256:5eb1516f1fe1813e9da8f6c482178fc067af09f53de587ae03887ef5d9d13024"}, - {file = "fakeredis-1.6.1.tar.gz", hash = "sha256:0d06a9384fb79da9f2164ce96e34eb9d4e2ea46215070805ea6fd3c174590b47"}, + {file = "fakeredis-1.7.0-py3-none-any.whl", hash = "sha256:6f1e04f64557ad3b6835bdc6e5a8d022cbace4bdc24a47ad58f6a72e0fbff760"}, + {file = "fakeredis-1.7.0.tar.gz", hash = "sha256:c9bd12e430336cbd3e189fae0e91eb99997b93e76dbfdd6ed67fa352dc684c71"}, ] filelock = [ - {file = "filelock-3.3.2-py3-none-any.whl", hash = "sha256:bb2a1c717df74c48a2d00ed625e5a66f8572a3a30baacb7657add1d7bac4097b"}, - {file = "filelock-3.3.2.tar.gz", hash = "sha256:7afc856f74fa7006a289fd10fa840e1eebd8bbff6bffb69c26c54a0512ea8cf8"}, + {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"}, + {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, @@ -1102,8 +1069,8 @@ humanfriendly = [ {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, ] identify = [ - {file = "identify-2.3.5-py2.py3-none-any.whl", hash = "sha256:ba945bddb4322394afcf3f703fa68eda08a6acc0f99d9573eb2be940aa7b9bba"}, - {file = "identify-2.3.5.tar.gz", hash = "sha256:6f0368ba0f21c199645a331beb7425d5374376e71bc149e9cb55e45cb45f832d"}, + {file = "identify-2.4.1-py2.py3-none-any.whl", hash = "sha256:0192893ff68b03d37fed553e261d4a22f94ea974093aefb33b29df2ff35fed3c"}, + {file = "identify-2.4.1.tar.gz", hash = "sha256:64d4885e539f505dd8ffb5e93c142a1db45480452b1594cacd3e91dca9a984e9"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -1113,136 +1080,67 @@ isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] -kiwisolver = [ - {file = "kiwisolver-1.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1d819553730d3c2724582124aee8a03c846ec4362ded1034c16fb3ef309264e6"}, - {file = "kiwisolver-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d93a1095f83e908fc253f2fb569c2711414c0bfd451cab580466465b235b470"}, - {file = "kiwisolver-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4550a359c5157aaf8507e6820d98682872b9100ce7607f8aa070b4b8af6c298"}, - {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2210f28778c7d2ee13f3c2a20a3a22db889e75f4ec13a21072eabb5693801e84"}, - {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:82f49c5a79d3839bc8f38cb5f4bfc87e15f04cbafa5fbd12fb32c941cb529cfb"}, - {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9661a04ca3c950a8ac8c47f53cbc0b530bce1b52f516a1e87b7736fec24bfff0"}, - {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ddb500a2808c100e72c075cbb00bf32e62763c82b6a882d403f01a119e3f402"}, - {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72be6ebb4e92520b9726d7146bc9c9b277513a57a38efcf66db0620aec0097e0"}, - {file = "kiwisolver-1.3.2-cp310-cp310-win32.whl", hash = "sha256:83d2c9db5dfc537d0171e32de160461230eb14663299b7e6d18ca6dca21e4977"}, - {file = "kiwisolver-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:cba430db673c29376135e695c6e2501c44c256a81495da849e85d1793ee975ad"}, - {file = "kiwisolver-1.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4116ba9a58109ed5e4cb315bdcbff9838f3159d099ba5259c7c7fb77f8537492"}, - {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19554bd8d54cf41139f376753af1a644b63c9ca93f8f72009d50a2080f870f77"}, - {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a4cf5bbdc861987a7745aed7a536c6405256853c94abc9f3287c3fa401b174"}, - {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0007840186bacfaa0aba4466d5890334ea5938e0bb7e28078a0eb0e63b5b59d5"}, - {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec2eba188c1906b05b9b49ae55aae4efd8150c61ba450e6721f64620c50b59eb"}, - {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3dbb3cea20b4af4f49f84cffaf45dd5f88e8594d18568e0225e6ad9dec0e7967"}, - {file = "kiwisolver-1.3.2-cp37-cp37m-win32.whl", hash = "sha256:5326ddfacbe51abf9469fe668944bc2e399181a2158cb5d45e1d40856b2a0589"}, - {file = "kiwisolver-1.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c6572c2dab23c86a14e82c245473d45b4c515314f1f859e92608dcafbd2f19b8"}, - {file = "kiwisolver-1.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b5074fb09429f2b7bc82b6fb4be8645dcbac14e592128beeff5461dcde0af09f"}, - {file = "kiwisolver-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:22521219ca739654a296eea6d4367703558fba16f98688bd8ce65abff36eaa84"}, - {file = "kiwisolver-1.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c358721aebd40c243894298f685a19eb0491a5c3e0b923b9f887ef1193ddf829"}, - {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ba5a1041480c6e0a8b11a9544d53562abc2d19220bfa14133e0cdd9967e97af"}, - {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44e6adf67577dbdfa2d9f06db9fbc5639afefdb5bf2b4dfec25c3a7fbc619536"}, - {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d45d1c74f88b9f41062716c727f78f2a59a5476ecbe74956fafb423c5c87a76"}, - {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70adc3658138bc77a36ce769f5f183169bc0a2906a4f61f09673f7181255ac9b"}, - {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6a5431940f28b6de123de42f0eb47b84a073ee3c3345dc109ad550a3307dd28"}, - {file = "kiwisolver-1.3.2-cp38-cp38-win32.whl", hash = "sha256:ee040a7de8d295dbd261ef2d6d3192f13e2b08ec4a954de34a6fb8ff6422e24c"}, - {file = "kiwisolver-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:8dc3d842fa41a33fe83d9f5c66c0cc1f28756530cd89944b63b072281e852031"}, - {file = "kiwisolver-1.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a498bcd005e8a3fedd0022bb30ee0ad92728154a8798b703f394484452550507"}, - {file = "kiwisolver-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80efd202108c3a4150e042b269f7c78643420cc232a0a771743bb96b742f838f"}, - {file = "kiwisolver-1.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f8eb7b6716f5b50e9c06207a14172cf2de201e41912ebe732846c02c830455b9"}, - {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f441422bb313ab25de7b3dbfd388e790eceb76ce01a18199ec4944b369017009"}, - {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:30fa008c172355c7768159983a7270cb23838c4d7db73d6c0f6b60dde0d432c6"}, - {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f8f6c8f4f1cff93ca5058d6ec5f0efda922ecb3f4c5fb76181f327decff98b8"}, - {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba677bcaff9429fd1bf01648ad0901cea56c0d068df383d5f5856d88221fe75b"}, - {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7843b1624d6ccca403a610d1277f7c28ad184c5aa88a1750c1a999754e65b439"}, - {file = "kiwisolver-1.3.2-cp39-cp39-win32.whl", hash = "sha256:e6f5eb2f53fac7d408a45fbcdeda7224b1cfff64919d0f95473420a931347ae9"}, - {file = "kiwisolver-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:eedd3b59190885d1ebdf6c5e0ca56828beb1949b4dfe6e5d0256a461429ac386"}, - {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dedc71c8eb9c5096037766390172c34fb86ef048b8e8958b4e484b9e505d66bc"}, - {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bf7eb45d14fc036514c09554bf983f2a72323254912ed0c3c8e697b62c4c158f"}, - {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b65bd35f3e06a47b5c30ea99e0c2b88f72c6476eedaf8cfbc8e66adb5479dcf"}, - {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25405f88a37c5f5bcba01c6e350086d65e7465fd1caaf986333d2a045045a223"}, - {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bcadb05c3d4794eb9eee1dddf1c24215c92fb7b55a80beae7a60530a91060560"}, - {file = "kiwisolver-1.3.2.tar.gz", hash = "sha256:fc4453705b81d03568d5b808ad8f09c77c47534f6ac2e72e733f9ca4714aa75c"}, -] lxml = [ - {file = "lxml-4.6.5-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:abcf7daa5ebcc89328326254f6dd6d566adb483d4d00178892afd386ab389de2"}, - {file = "lxml-4.6.5-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3884476a90d415be79adfa4e0e393048630d0d5bcd5757c4c07d8b4b00a1096b"}, - {file = "lxml-4.6.5-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:add017c5bd6b9ec3a5f09248396b6ee2ce61c5621f087eb2269c813cd8813808"}, - {file = "lxml-4.6.5-cp27-cp27m-win32.whl", hash = "sha256:a702005e447d712375433ed0499cb6e1503fadd6c96a47f51d707b4d37b76d3c"}, - {file = "lxml-4.6.5-cp27-cp27m-win_amd64.whl", hash = "sha256:da07c7e7fc9a3f40446b78c54dbba8bfd5c9100dfecb21b65bfe3f57844f5e71"}, - {file = "lxml-4.6.5-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a708c291900c40a7ecf23f1d2384ed0bc0604e24094dd13417c7e7f8f7a50d93"}, - {file = "lxml-4.6.5-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f33d8efb42e4fc2b31b3b4527940b25cdebb3026fb56a80c1c1c11a4271d2352"}, - {file = "lxml-4.6.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:f6befb83bca720b71d6bd6326a3b26e9496ae6649e26585de024890fe50f49b8"}, - {file = "lxml-4.6.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:59d77bfa3bea13caee95bc0d3f1c518b15049b97dd61ea8b3d71ce677a67f808"}, - {file = "lxml-4.6.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:68a851176c931e2b3de6214347b767451243eeed3bea34c172127bbb5bf6c210"}, - {file = "lxml-4.6.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a7790a273225b0c46e5f859c1327f0f659896cc72eaa537d23aa3ad9ff2a1cc1"}, - {file = "lxml-4.6.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6548fc551de15f310dd0564751d9dc3d405278d45ea9b2b369ed1eccf142e1f5"}, - {file = "lxml-4.6.5-cp310-cp310-win32.whl", hash = "sha256:dc8a0dbb2a10ae8bb609584f5c504789f0f3d0d81840da4849102ec84289f952"}, - {file = "lxml-4.6.5-cp310-cp310-win_amd64.whl", hash = "sha256:1ccbfe5d17835db906f2bab6f15b34194db1a5b07929cba3cf45a96dbfbfefc0"}, - {file = "lxml-4.6.5-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca9a40497f7e97a2a961c04fa8a6f23d790b0521350a8b455759d786b0bcb203"}, - {file = "lxml-4.6.5-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e5b4b0d9440046ead3bd425eb2b852499241ee0cef1ae151038e4f87ede888c4"}, - {file = "lxml-4.6.5-cp35-cp35m-win32.whl", hash = "sha256:87f8f7df70b90fbe7b49969f07b347e3f978f8bd1046bb8ecae659921869202b"}, - {file = "lxml-4.6.5-cp35-cp35m-win_amd64.whl", hash = "sha256:ce52aad32ec6e46d1a91ff8b8014a91538800dd533914bfc4a82f5018d971408"}, - {file = "lxml-4.6.5-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:8021eeff7fabde21b9858ed058a8250ad230cede91764d598c2466b0ba70db8b"}, - {file = "lxml-4.6.5-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:cab343b265e38d4e00649cbbad9278b734c5715f9bcbb72c85a1f99b1a58e19a"}, - {file = "lxml-4.6.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3534d7c468c044f6aef3c0aff541db2826986a29ea73f2ca831f5d5284d9b570"}, - {file = "lxml-4.6.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdb98f4c9e8a1735efddfaa995b0c96559792da15d56b76428bdfc29f77c4cdb"}, - {file = "lxml-4.6.5-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5ea121cb66d7e5cb396b4c3ca90471252b94e01809805cfe3e4e44be2db3a99c"}, - {file = "lxml-4.6.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:121fc6f71c692b49af6c963b84ab7084402624ffbe605287da362f8af0668ea3"}, - {file = "lxml-4.6.5-cp36-cp36m-win32.whl", hash = "sha256:1a2a7659b8eb93c6daee350a0d844994d49245a0f6c05c747f619386fb90ba04"}, - {file = "lxml-4.6.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2f77556266a8fe5428b8759fbfc4bd70be1d1d9c9b25d2a414f6a0c0b0f09120"}, - {file = "lxml-4.6.5-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:558485218ee06458643b929765ac1eb04519ca3d1e2dcc288517de864c747c33"}, - {file = "lxml-4.6.5-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ba0006799f21d83c3717fe20e2707a10bbc296475155aadf4f5850f6659b96b9"}, - {file = "lxml-4.6.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:916d457ad84e05b7db52700bad0a15c56e0c3000dcaf1263b2fb7a56fe148996"}, - {file = "lxml-4.6.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c580c2a61d8297a6e47f4d01f066517dbb019be98032880d19ece7f337a9401d"}, - {file = "lxml-4.6.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a21b78af7e2e13bec6bea12fc33bc05730197674f3e5402ce214d07026ccfebd"}, - {file = "lxml-4.6.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:46515773570a33eae13e451c8fcf440222ef24bd3b26f40774dd0bd8b6db15b2"}, - {file = "lxml-4.6.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:124f09614f999551ac65e5b9875981ce4b66ac4b8e2ba9284572f741935df3d9"}, - {file = "lxml-4.6.5-cp37-cp37m-win32.whl", hash = "sha256:b4015baed99d046c760f09a4c59d234d8f398a454380c3cf0b859aba97136090"}, - {file = "lxml-4.6.5-cp37-cp37m-win_amd64.whl", hash = "sha256:12ae2339d32a2b15010972e1e2467345b7bf962e155671239fba74c229564b7f"}, - {file = "lxml-4.6.5-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:76b6c296e4f7a1a8a128aec42d128646897f9ae9a700ef6839cdc9b3900db9b5"}, - {file = "lxml-4.6.5-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:534032a5ceb34bba1da193b7d386ac575127cc39338379f39a164b10d97ade89"}, - {file = "lxml-4.6.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:60aeb14ff9022d2687ef98ce55f6342944c40d00916452bb90899a191802137a"}, - {file = "lxml-4.6.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9801bcd52ac9c795a7d81ea67471a42cffe532e46cfb750cd5713befc5c019c0"}, - {file = "lxml-4.6.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b95fb7e6f9c2f53db88f4642231fc2b8907d854e614710996a96f1f32018d5c"}, - {file = "lxml-4.6.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:642eb4cabd997c9b949a994f9643cd8ae00cf4ca8c5cd9c273962296fadf1c44"}, - {file = "lxml-4.6.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af4139172ff0263d269abdcc641e944c9de4b5d660894a3ec7e9f9db63b56ac9"}, - {file = "lxml-4.6.5-cp38-cp38-win32.whl", hash = "sha256:57cf05466917e08f90e323f025b96f493f92c0344694f5702579ab4b7e2eb10d"}, - {file = "lxml-4.6.5-cp38-cp38-win_amd64.whl", hash = "sha256:4f415624cf8b065796649a5e4621773dc5c9ea574a944c76a7f8a6d3d2906b41"}, - {file = "lxml-4.6.5-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7679bb6e4d9a3978a46ab19a3560e8d2b7265ef3c88152e7fdc130d649789887"}, - {file = "lxml-4.6.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c34234a1bc9e466c104372af74d11a9f98338a3f72fae22b80485171a64e0144"}, - {file = "lxml-4.6.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4b9390bf973e3907d967b75be199cf1978ca8443183cf1e78ad80ad8be9cf242"}, - {file = "lxml-4.6.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fcc849b28f584ed1dbf277291ded5c32bb3476a37032df4a1d523b55faa5f944"}, - {file = "lxml-4.6.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:46f21f2600d001af10e847df9eb3b832e8a439f696c04891bcb8a8cedd859af9"}, - {file = "lxml-4.6.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:99cf827f5a783038eb313beee6533dddb8bdb086d7269c5c144c1c952d142ace"}, - {file = "lxml-4.6.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:925174cafb0f1179a7fd38da90302555d7445e34c9ece68019e53c946be7f542"}, - {file = "lxml-4.6.5-cp39-cp39-win32.whl", hash = "sha256:12d8d6fe3ddef629ac1349fa89a638b296a34b6529573f5055d1cb4e5245f73b"}, - {file = "lxml-4.6.5-cp39-cp39-win_amd64.whl", hash = "sha256:a52e8f317336a44836475e9c802f51c2dc38d612eaa76532cb1d17690338b63b"}, - {file = "lxml-4.6.5-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:11ae552a78612620afd15625be9f1b82e3cc2e634f90d6b11709b10a100cba59"}, - {file = "lxml-4.6.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:473701599665d874919d05bb33b56180447b3a9da8d52d6d9799f381ce23f95c"}, - {file = "lxml-4.6.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7f00cc64b49d2ef19ddae898a3def9dd8fda9c3d27c8a174c2889ee757918e71"}, - {file = "lxml-4.6.5-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:73e8614258404b2689a26cb5d002512b8bc4dfa18aca86382f68f959aee9b0c8"}, - {file = "lxml-4.6.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ff44de36772b05c2eb74f2b4b6d1ae29b8f41ed5506310ce1258d44826ee38c1"}, - {file = "lxml-4.6.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5d5254c815c186744c8f922e2ce861a2bdeabc06520b4b30b2f7d9767791ce6e"}, - {file = "lxml-4.6.5.tar.gz", hash = "sha256:6e84edecc3a82f90d44ddee2ee2a2630d4994b8471816e226d2b771cda7ac4ca"}, -] -matplotlib = [ - {file = "matplotlib-3.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c988bb43414c7c2b0a31bd5187b4d27fd625c080371b463a6d422047df78913"}, - {file = "matplotlib-3.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f1c5efc278d996af8a251b2ce0b07bbeccb821f25c8c9846bdcb00ffc7f158aa"}, - {file = "matplotlib-3.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eeb1859efe7754b1460e1d4991bbd4a60a56f366bc422ef3a9c5ae05f0bc70b5"}, - {file = "matplotlib-3.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:844a7b0233e4ff7fba57e90b8799edaa40b9e31e300b8d5efc350937fa8b1bea"}, - {file = "matplotlib-3.4.3-cp37-cp37m-win32.whl", hash = "sha256:85f0c9cf724715e75243a7b3087cf4a3de056b55e05d4d76cc58d610d62894f3"}, - {file = "matplotlib-3.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c70b6311dda3e27672f1bf48851a0de816d1ca6aaf3d49365fbdd8e959b33d2b"}, - {file = "matplotlib-3.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b884715a59fec9ad3b6048ecf3860f3b2ce965e676ef52593d6fa29abcf7d330"}, - {file = "matplotlib-3.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a78a3b51f29448c7f4d4575e561f6b0dbb8d01c13c2046ab6c5220eb25c06506"}, - {file = "matplotlib-3.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a724e3a48a54b8b6e7c4ae38cd3d07084508fa47c410c8757e9db9791421838"}, - {file = "matplotlib-3.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48e1e0859b54d5f2e29bb78ca179fd59b971c6ceb29977fb52735bfd280eb0f5"}, - {file = "matplotlib-3.4.3-cp38-cp38-win32.whl", hash = "sha256:01c9de93a2ca0d128c9064f23709362e7fefb34910c7c9e0b8ab0de8258d5eda"}, - {file = "matplotlib-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:ebfb01a65c3f5d53a8c2a8133fec2b5221281c053d944ae81ff5822a68266617"}, - {file = "matplotlib-3.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b53f336a4688cfce615887505d7e41fd79b3594bf21dd300531a4f5b4f746a"}, - {file = "matplotlib-3.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:fcd6f1954943c0c192bfbebbac263f839d7055409f1173f80d8b11a224d236da"}, - {file = "matplotlib-3.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6be8df61b1626e1a142c57e065405e869e9429b4a6dab4a324757d0dc4d42235"}, - {file = "matplotlib-3.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:41b6e307458988891fcdea2d8ecf84a8c92d53f84190aa32da65f9505546e684"}, - {file = "matplotlib-3.4.3-cp39-cp39-win32.whl", hash = "sha256:f72657f1596199dc1e4e7a10f52a4784ead8a711f4e5b59bea95bdb97cf0e4fd"}, - {file = "matplotlib-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:f15edcb0629a0801738925fe27070480f446fcaa15de65946ff946ad99a59a40"}, - {file = "matplotlib-3.4.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:556965514b259204637c360d213de28d43a1f4aed1eca15596ce83f768c5a56f"}, - {file = "matplotlib-3.4.3-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:54a026055d5f8614f184e588f6e29064019a0aa8448450214c0b60926d62d919"}, - {file = "matplotlib-3.4.3.tar.gz", hash = "sha256:fc4f526dfdb31c9bd6b8ca06bf9fab663ca12f3ec9cdf4496fb44bc680140318"}, + {file = "lxml-4.7.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:d546431636edb1d6a608b348dd58cc9841b81f4116745857b6cb9f8dadb2725f"}, + {file = "lxml-4.7.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6308062534323f0d3edb4e702a0e26a76ca9e0e23ff99be5d82750772df32a9e"}, + {file = "lxml-4.7.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f76dbe44e31abf516114f6347a46fa4e7c2e8bceaa4b6f7ee3a0a03c8eba3c17"}, + {file = "lxml-4.7.1-cp27-cp27m-win32.whl", hash = "sha256:d5618d49de6ba63fe4510bdada62d06a8acfca0b4b5c904956c777d28382b419"}, + {file = "lxml-4.7.1-cp27-cp27m-win_amd64.whl", hash = "sha256:9393a05b126a7e187f3e38758255e0edf948a65b22c377414002d488221fdaa2"}, + {file = "lxml-4.7.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:50d3dba341f1e583265c1a808e897b4159208d814ab07530202b6036a4d86da5"}, + {file = "lxml-4.7.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44f552e0da3c8ee3c28e2eb82b0b784200631687fc6a71277ea8ab0828780e7d"}, + {file = "lxml-4.7.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:e662c6266e3a275bdcb6bb049edc7cd77d0b0f7e119a53101d367c841afc66dc"}, + {file = "lxml-4.7.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4c093c571bc3da9ebcd484e001ba18b8452903cd428c0bc926d9b0141bcb710e"}, + {file = "lxml-4.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3e26ad9bc48d610bf6cc76c506b9e5ad9360ed7a945d9be3b5b2c8535a0145e3"}, + {file = "lxml-4.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a5f623aeaa24f71fce3177d7fee875371345eb9102b355b882243e33e04b7175"}, + {file = "lxml-4.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b5e2acefd33c259c4a2e157119c4373c8773cf6793e225006a1649672ab47a6"}, + {file = "lxml-4.7.1-cp310-cp310-win32.whl", hash = "sha256:67fa5f028e8a01e1d7944a9fb616d1d0510d5d38b0c41708310bd1bc45ae89f6"}, + {file = "lxml-4.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:b1d381f58fcc3e63fcc0ea4f0a38335163883267f77e4c6e22d7a30877218a0e"}, + {file = "lxml-4.7.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:38d9759733aa04fb1697d717bfabbedb21398046bd07734be7cccc3d19ea8675"}, + {file = "lxml-4.7.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dfd0d464f3d86a1460683cd742306d1138b4e99b79094f4e07e1ca85ee267fe7"}, + {file = "lxml-4.7.1-cp35-cp35m-win32.whl", hash = "sha256:534e946bce61fd162af02bad7bfd2daec1521b71d27238869c23a672146c34a5"}, + {file = "lxml-4.7.1-cp35-cp35m-win_amd64.whl", hash = "sha256:6ec829058785d028f467be70cd195cd0aaf1a763e4d09822584ede8c9eaa4b03"}, + {file = "lxml-4.7.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:ade74f5e3a0fd17df5782896ddca7ddb998845a5f7cd4b0be771e1ffc3b9aa5b"}, + {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41358bfd24425c1673f184d7c26c6ae91943fe51dfecc3603b5e08187b4bcc55"}, + {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6e56521538f19c4a6690f439fefed551f0b296bd785adc67c1777c348beb943d"}, + {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b0f782f0e03555c55e37d93d7a57454efe7495dab33ba0ccd2dbe25fc50f05d"}, + {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:490712b91c65988012e866c411a40cc65b595929ececf75eeb4c79fcc3bc80a6"}, + {file = "lxml-4.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c22eb8c819d59cec4444d9eebe2e38b95d3dcdafe08965853f8799fd71161d"}, + {file = "lxml-4.7.1-cp36-cp36m-win32.whl", hash = "sha256:2a906c3890da6a63224d551c2967413b8790a6357a80bf6b257c9a7978c2c42d"}, + {file = "lxml-4.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:36b16fecb10246e599f178dd74f313cbdc9f41c56e77d52100d1361eed24f51a"}, + {file = "lxml-4.7.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a5edc58d631170de90e50adc2cc0248083541affef82f8cd93bea458e4d96db8"}, + {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:87c1b0496e8c87ec9db5383e30042357b4839b46c2d556abd49ec770ce2ad868"}, + {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0a5f0e4747f31cff87d1eb32a6000bde1e603107f632ef4666be0dc065889c7a"}, + {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bf6005708fc2e2c89a083f258b97709559a95f9a7a03e59f805dd23c93bc3986"}, + {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc15874816b9320581133ddc2096b644582ab870cf6a6ed63684433e7af4b0d3"}, + {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0b5e96e25e70917b28a5391c2ed3ffc6156513d3db0e1476c5253fcd50f7a944"}, + {file = "lxml-4.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ec9027d0beb785a35aa9951d14e06d48cfbf876d8ff67519403a2522b181943b"}, + {file = "lxml-4.7.1-cp37-cp37m-win32.whl", hash = "sha256:9fbc0dee7ff5f15c4428775e6fa3ed20003140560ffa22b88326669d53b3c0f4"}, + {file = "lxml-4.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1104a8d47967a414a436007c52f533e933e5d52574cab407b1e49a4e9b5ddbd1"}, + {file = "lxml-4.7.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:fc9fb11b65e7bc49f7f75aaba1b700f7181d95d4e151cf2f24d51bfd14410b77"}, + {file = "lxml-4.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:317bd63870b4d875af3c1be1b19202de34c32623609ec803b81c99193a788c1e"}, + {file = "lxml-4.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:610807cea990fd545b1559466971649e69302c8a9472cefe1d6d48a1dee97440"}, + {file = "lxml-4.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:09b738360af8cb2da275998a8bf79517a71225b0de41ab47339c2beebfff025f"}, + {file = "lxml-4.7.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a2ab9d089324d77bb81745b01f4aeffe4094306d939e92ba5e71e9a6b99b71e"}, + {file = "lxml-4.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eed394099a7792834f0cb4a8f615319152b9d801444c1c9e1b1a2c36d2239f9e"}, + {file = "lxml-4.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:735e3b4ce9c0616e85f302f109bdc6e425ba1670a73f962c9f6b98a6d51b77c9"}, + {file = "lxml-4.7.1-cp38-cp38-win32.whl", hash = "sha256:772057fba283c095db8c8ecde4634717a35c47061d24f889468dc67190327bcd"}, + {file = "lxml-4.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:13dbb5c7e8f3b6a2cf6e10b0948cacb2f4c9eb05029fe31c60592d08ac63180d"}, + {file = "lxml-4.7.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:718d7208b9c2d86aaf0294d9381a6acb0158b5ff0f3515902751404e318e02c9"}, + {file = "lxml-4.7.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:5bee1b0cbfdb87686a7fb0e46f1d8bd34d52d6932c0723a86de1cc532b1aa489"}, + {file = "lxml-4.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e410cf3a2272d0a85526d700782a2fa92c1e304fdcc519ba74ac80b8297adf36"}, + {file = "lxml-4.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:585ea241ee4961dc18a95e2f5581dbc26285fcf330e007459688096f76be8c42"}, + {file = "lxml-4.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a555e06566c6dc167fbcd0ad507ff05fd9328502aefc963cb0a0547cfe7f00db"}, + {file = "lxml-4.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:adaab25be351fff0d8a691c4f09153647804d09a87a4e4ea2c3f9fe9e8651851"}, + {file = "lxml-4.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:82d16a64236970cb93c8d63ad18c5b9f138a704331e4b916b2737ddfad14e0c4"}, + {file = "lxml-4.7.1-cp39-cp39-win32.whl", hash = "sha256:59e7da839a1238807226f7143c68a479dee09244d1b3cf8c134f2fce777d12d0"}, + {file = "lxml-4.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:a1bbc4efa99ed1310b5009ce7f3a1784698082ed2c1ef3895332f5df9b3b92c2"}, + {file = "lxml-4.7.1-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:0607ff0988ad7e173e5ddf7bf55ee65534bd18a5461183c33e8e41a59e89edf4"}, + {file = "lxml-4.7.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:6c198bfc169419c09b85ab10cb0f572744e686f40d1e7f4ed09061284fc1303f"}, + {file = "lxml-4.7.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a58d78653ae422df6837dd4ca0036610b8cb4962b5cfdbd337b7b24de9e5f98a"}, + {file = "lxml-4.7.1-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:e18281a7d80d76b66a9f9e68a98cf7e1d153182772400d9a9ce855264d7d0ce7"}, + {file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8e54945dd2eeb50925500957c7c579df3cd07c29db7810b83cf30495d79af267"}, + {file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:447d5009d6b5447b2f237395d0018901dcc673f7d9f82ba26c1b9f9c3b444b60"}, + {file = "lxml-4.7.1.tar.gz", hash = "sha256:a1613838aa6b89af4ba10a0f3a972836128801ed008078f8c1244e65958f1b24"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -1330,39 +1228,9 @@ nodeenv = [ {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] -numpy = [ - {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"}, - {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"}, - {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"}, - {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"}, - {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"}, - {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"}, - {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"}, - {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"}, - {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"}, - {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"}, - {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"}, - {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"}, - {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"}, - {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"}, - {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"}, - {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"}, - {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"}, - {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"}, - {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"}, - {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"}, - {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"}, - {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"}, - {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"}, - {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"}, - {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"}, - {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"}, - {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"}, - {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"}, -] packaging = [ - {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, - {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pep8-naming = [ {file = "pep8-naming-0.12.1.tar.gz", hash = "sha256:bb2455947757d162aa4cad55dba4ce029005cd1692f2899a21d51d8630ca7841"}, @@ -1416,12 +1284,12 @@ pip-licenses = [ {file = "pip_licenses-3.5.3-py3-none-any.whl", hash = "sha256:59c148d6a03784bf945d232c0dc0e9de4272a3675acaa0361ad7712398ca86ba"}, ] platformdirs = [ - {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, - {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, + {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, + {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, ] pre-commit = [ - {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"}, - {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"}, + {file = "pre_commit-2.16.0-py2.py3-none-any.whl", hash = "sha256:758d1dc9b62c2ed8881585c254976d66eae0889919ab9b859064fc2fe3c7743e"}, + {file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"}, ] psutil = [ {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, @@ -1506,8 +1374,8 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pyparsing = [ - {file = "pyparsing-3.0.5-py3-none-any.whl", hash = "sha256:4881e3d2979f27b41a3a2421b10be9cbfa7ce2baa6c7117952222f8bbea6650c"}, - {file = "pyparsing-3.0.5.tar.gz", hash = "sha256:9329d1c1b51f0f76371c4ded42c5ec4cc0be18456b22193e0570c2da98ed288b"}, + {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, + {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, ] pyreadline3 = [ {file = "pyreadline3-3.3-py3-none-any.whl", hash = "sha256:0003fd0079d152ecbd8111202c5a7dfa6a5569ffd65b235e45f3c2ecbee337b4"}, @@ -1518,8 +1386,8 @@ python-dateutil = [ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] python-dotenv = [ - {file = "python-dotenv-0.19.1.tar.gz", hash = "sha256:14f8185cc8d494662683e6914addcb7e95374771e707601dfc70166946b4c4b8"}, - {file = "python_dotenv-0.19.1-py2.py3-none-any.whl", hash = "sha256:bbd3da593fc49c249397cbfbcc449cf36cb02e75afc8157fcc6a81df6fb7750a"}, + {file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"}, + {file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"}, ] pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, @@ -1553,61 +1421,62 @@ pyyaml = [ {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] rapidfuzz = [ - {file = "rapidfuzz-1.8.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d218a4aac1488cc7d63f1a597a33863aa304f6ac590d70057e708ec6865a4118"}, - {file = "rapidfuzz-1.8.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:8e47b2f9f49edbfd582f6703cde54d22ffa98d83c8393ccd07073852b33832ea"}, - {file = "rapidfuzz-1.8.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:17e456d2ba6bee54b5c83bb403c33e02c3873958e153c0413021a2a042b0940d"}, - {file = "rapidfuzz-1.8.2-cp27-cp27m-win32.whl", hash = "sha256:ab067c4f04f037686d6cad1a7fce4c3998548f38778f0edb351280b902b8b3e1"}, - {file = "rapidfuzz-1.8.2-cp27-cp27m-win_amd64.whl", hash = "sha256:6c3e298aa955b164c85e7e0e2372805da1d6bae7399dad256211caafdab46e7f"}, - {file = "rapidfuzz-1.8.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f62471a08d2c46c79a8e0129b74c2997e62692c36bef47a7392b542d6dafc6bf"}, - {file = "rapidfuzz-1.8.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:271ce29c63eb87d76bcd384753cdfbfb8f2a0aeb3c7d0891787b1f19b007a0e8"}, - {file = "rapidfuzz-1.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22f49eb345759602cc002200217a62564d947f65a568723f99b80c74027ce77b"}, - {file = "rapidfuzz-1.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4ec6015b74cdbd4ac8bbbf280522e913f11c4656e334559a8454713def4f3b33"}, - {file = "rapidfuzz-1.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d53a474e3b7592a2ff3d4c1e545324314845d3ccbbc80f6dd89d044bceaf8a59"}, - {file = "rapidfuzz-1.8.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:242ebe941991a086b9e455dadcdaf624bb895457e3ce254b0b51a7b9adc68fed"}, - {file = "rapidfuzz-1.8.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dab9822666819350aa32008fe35a7c7da422289d6961a01a2e00e99c2581e7fe"}, - {file = "rapidfuzz-1.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8107e175577f200a2426c4896b8e17a97b86f513122cba4155c7462f0fcc18ac"}, - {file = "rapidfuzz-1.8.2-cp310-cp310-win32.whl", hash = "sha256:526d92f1ff354303cba0a4cbf184d11b94395841d00eaecf8963c6dc89deec21"}, - {file = "rapidfuzz-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:55168678ae7df1ad8099ec2f0ce54024c924b375b18a6f5d3237c930083fcfca"}, - {file = "rapidfuzz-1.8.2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:23c0632047bf8fac891ef73dfd397e951514d11fb5f168b630e3569ffcd51d61"}, - {file = "rapidfuzz-1.8.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:90d0e10c7075f761b17e590cf9254b32e7084de7e2b4cd11201031c61386714e"}, - {file = "rapidfuzz-1.8.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:c9537e7f2a489654671c4e3bdc3e01d1116052b3889217da7fe2f0b0f0b27e10"}, - {file = "rapidfuzz-1.8.2-cp35-cp35m-win32.whl", hash = "sha256:8ded55e5395af1705bbc89ab94bea3c73218e1f71ae2b72cd905a827ab131fa1"}, - {file = "rapidfuzz-1.8.2-cp35-cp35m-win_amd64.whl", hash = "sha256:2d4240fce22e74c6f3d381201288cea6cc5af8d310ec271b573b26558c2eaec8"}, - {file = "rapidfuzz-1.8.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:90901b7be174b1f14455c5601eff46b03b174cf1d61cec0fd6d37c74dd727c88"}, - {file = "rapidfuzz-1.8.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7dbd1524e0f2d4290cd60fe1474068a8d8a27a41e1dfa14b1e4d53735529a11c"}, - {file = "rapidfuzz-1.8.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b8eee29e35ce51adf1cbfcc5b64d1a89d71401a5101b8fe9b87abbe6bf4f893"}, - {file = "rapidfuzz-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:648fd2e561b6beb208e6cf46eb9f20ba952add69c2f90fb4e46898a821bca4c9"}, - {file = "rapidfuzz-1.8.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:40dacd641d93708818ff2c9921eb6e5b9463e9b3b7f410dde3c96e0e0f80c414"}, - {file = "rapidfuzz-1.8.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:28528d34fb73f991e2822fdf1aa28eff7832c2189c8d2d0b598f15dd1b1b7d88"}, - {file = "rapidfuzz-1.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f46137937dfa2ec04fbb9f0225ff3160b9f1ed993dc5abf126ccdeae38726c5"}, - {file = "rapidfuzz-1.8.2-cp37-cp37m-win32.whl", hash = "sha256:709ea3151a7448bb15adc3212cbdd4766eca557f1ae73cdff8ae656b6899e71a"}, - {file = "rapidfuzz-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c426eb833d16ddeec95c966efa78492803d741c85cf6553febf78979fc267692"}, - {file = "rapidfuzz-1.8.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:91e20676b562c972de8af6a153df6eaaea27da96ade8321e51da7bab32285242"}, - {file = "rapidfuzz-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ea3c3c1a5401affeb7d279c2b7cf418d5e7d7e170eb010fcb1b45bb28de5e4d1"}, - {file = "rapidfuzz-1.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ab21f86812a7da111a17c118fc8e827b4c289d02f39536f7a6acc6baf479dcc0"}, - {file = "rapidfuzz-1.8.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0eef79b223e239af5cc9c0b89ae8096a38bc8a9109474ff610151ea0115b931c"}, - {file = "rapidfuzz-1.8.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:20dfda3bd9c14e6d7951360af66792a15e81051fa017db00c71bad4b1e25688d"}, - {file = "rapidfuzz-1.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec24328d855b88e8a472379c61af9972f0d7992edfe2ebaca03afed2c5282f94"}, - {file = "rapidfuzz-1.8.2-cp38-cp38-win32.whl", hash = "sha256:69ab203a4b59c7e9ddec96044bd684d6165eab3362f84677822936fea2e8c4f1"}, - {file = "rapidfuzz-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:bd039be9798a3deba25301827b86b3ff084d69e2b4b16ae8da6a34638235680c"}, - {file = "rapidfuzz-1.8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7219ca078df644aa0694af9174b7abb09a75302021f1e062f39fcf183cb9f087"}, - {file = "rapidfuzz-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d240b7cd8dc7890c103adf4666a43fb3b3d1e5b1231e59aa3986e18dba1d086f"}, - {file = "rapidfuzz-1.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06f639a65f1269a52679942437228c804a354618dda79b68a70612c5c0a36f4e"}, - {file = "rapidfuzz-1.8.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:750ea6977e68a17f16308ac044017f8ce35c8a553698696f5a4fbb77e3110c8c"}, - {file = "rapidfuzz-1.8.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:517f26474012dbce37358a447165ca3b100b9b1b83ee8b3d6877accfdb209d46"}, - {file = "rapidfuzz-1.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b2bf70a24e129af11e217d94cb34f96a1487606bf4a9d8c8f16bc49c9465b56"}, - {file = "rapidfuzz-1.8.2-cp39-cp39-win32.whl", hash = "sha256:02ec1154e62493c0cb66c8b058d7196de48acbee7e884b757cff7bf075252232"}, - {file = "rapidfuzz-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:be564aea09ec8da0478b67f35be73f8c129054d45da7f4b02cd7e035d1aea96d"}, - {file = "rapidfuzz-1.8.2-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:54406d07788ca85aff12134425a2b931e842ee2c3b1ae915523cdd3d104c1136"}, - {file = "rapidfuzz-1.8.2-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:5648fcf1cbeacb05ea4b441ddf99c7f5e15dd53b7d3192e7449581b638e8b3f7"}, - {file = "rapidfuzz-1.8.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7d1d6ab126f960aa0df10454a06d17e303680df6370cc3c44c1f0697c4587c5c"}, - {file = "rapidfuzz-1.8.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1412ec4d43f0d1854ed3dd220dacbb68b977519553e80a2c4a250d3569793f92"}, - {file = "rapidfuzz-1.8.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:9dd34ad050ccb680d9dbf9871a53e1f01b084488d5310349c3e9cedcb4561eb2"}, - {file = "rapidfuzz-1.8.2.tar.gz", hash = "sha256:d6efbb2b6b18b3a67d7bdfbcd9bb72732f55736852bbef823bdf210f9e0c6c90"}, + {file = "rapidfuzz-1.9.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:68227a8b25291d6a2140aef049271ea30a77be5ef672a58e582a55a5cc1fce93"}, + {file = "rapidfuzz-1.9.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c33541995b96ff40025c1456b8c74b7dd2ab9cbf91943fc35a7bb621f48940e2"}, + {file = "rapidfuzz-1.9.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:c2fafbbf97a4632822248f4201601b691e2eac5fdb30e5d7a96d07a6d058a7d4"}, + {file = "rapidfuzz-1.9.1-cp27-cp27m-win32.whl", hash = "sha256:364795f617a99e1dbb55ac3947ab8366588b72531cb2d6152666287d20610706"}, + {file = "rapidfuzz-1.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:f171d9e66144b0647f9b998ef10bdd919a640e4b1357250c8ef6259deb5ffe0d"}, + {file = "rapidfuzz-1.9.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c83801a7c5209663aa120b815a4f2c39e95fe8e0b774ec58a1e0affd6a2fcfc6"}, + {file = "rapidfuzz-1.9.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:67e61c2baa6bb1848c4a33752f1781124dcc90bf3f31b18b44db1ae4e4e26634"}, + {file = "rapidfuzz-1.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8ab7eb003a18991347174910f11d38ff40399081185d9e3199ec277535f7828b"}, + {file = "rapidfuzz-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5ad450badf06ddf98a246140b5059ba895ee8445e8102a5a289908327f551f81"}, + {file = "rapidfuzz-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:402b2174bded62a793c5f7d9aec16bc32c661402360a934819ae72b54cfbce1e"}, + {file = "rapidfuzz-1.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92066ccb054efc2e17afb4049c98b550969653cd58f71dd756cfcc8e6864630a"}, + {file = "rapidfuzz-1.9.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8dc0bf1814accee08a9c9bace6672ef06eae6b0446fce88e3e97e23dfaf3ea10"}, + {file = "rapidfuzz-1.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdbd387efb8478605951344f327dd03bf053c138d757369a43404305b99e55db"}, + {file = "rapidfuzz-1.9.1-cp310-cp310-win32.whl", hash = "sha256:b1c54807e556dbcc6caf4ce0f24446c01b195f3cc46e2a6e74b82d3a21eaa45d"}, + {file = "rapidfuzz-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:ac3273364cd1619cab3bf0ba731efea5405833f9eba362da7dcd70bd42073d8e"}, + {file = "rapidfuzz-1.9.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:d9faf62606c08a0a6992dd480c72b6a068733ae02688dc35f2e36ba0d44673f4"}, + {file = "rapidfuzz-1.9.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:f6a56a48be047637b1b0b2459a11cf7cd5aa7bbe16a439bd4f73b4af39e620e4"}, + {file = "rapidfuzz-1.9.1-cp35-cp35m-win32.whl", hash = "sha256:aa91609979e9d2700f0ff100df99b36e7d700b70169ee385d43d5de9e471ae97"}, + {file = "rapidfuzz-1.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b4cfdd0915ab4cec86c2ff6bab9f01b03454f3de0963c37f9f219df2ddf42b95"}, + {file = "rapidfuzz-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6bfa4ad0158a093cd304f795ceefdc3861ae6942a61432b2a50858be6de88ca"}, + {file = "rapidfuzz-1.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:eb0ea02295d9278bd2dcd2df4760b0f2887b6c3f2f374005ec5af320d8d3a37e"}, + {file = "rapidfuzz-1.9.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5187cd5cd6273e9fee07de493a42a2153134a4914df74cb1abb0744551c548a"}, + {file = "rapidfuzz-1.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6e5b8af63f9c05b64454460759ed84a715d581d598ec4484f4ec512f398e8b1"}, + {file = "rapidfuzz-1.9.1-cp36-cp36m-win32.whl", hash = "sha256:36137f88f2b28115af506118e64e11c816611eab2434293af7fdacd1290ffb9d"}, + {file = "rapidfuzz-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:fcc420cad46be7c9887110edf04cdee545f26dbf22650a443d89790fc35f7b88"}, + {file = "rapidfuzz-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b06de314f426aebff8a44319016bbe2b22f7848c84e44224f80b0690b7b08b18"}, + {file = "rapidfuzz-1.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e5de44e719faea79e45322b037f0d4a141d750b80d2204fa68f43a42a24f0fbc"}, + {file = "rapidfuzz-1.9.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f9439df09a782afd01b67005a3b110c70bbf9e1cf06d2ac9b293ce2d02d3c549"}, + {file = "rapidfuzz-1.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e903d4702647465721e2d0431c95f04fd56a06577f06f41e2960c83fd63c1bad"}, + {file = "rapidfuzz-1.9.1-cp37-cp37m-win32.whl", hash = "sha256:a5298f4ac1975edcbb15583eab659a44b33aebaf3bccf172e185cfea68771c08"}, + {file = "rapidfuzz-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:103193a01921b54fcdad6b01cfda3a68e00aeafca236b7ecd5b1b2c2e7e96337"}, + {file = "rapidfuzz-1.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1d98a3187040dca855e02179a35c137f72ef83ce243783d44ea59efa86b94b3a"}, + {file = "rapidfuzz-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb92bf7fc911b787055a88d9295ca3b4fe8576e3b59271f070f1b1b181eb087d"}, + {file = "rapidfuzz-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3f014a0f5f8159a94c6ee884fedd1c30e07fb866a5d76ff2c18091bc6363b76f"}, + {file = "rapidfuzz-1.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:31474074a99f72289ac325fbd77983e7d355d48860bfe7a4f6f6396fdb24410a"}, + {file = "rapidfuzz-1.9.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec67d79af5a2d7b0cf67b570a5579710e461cadda4120478e813b63491f394dd"}, + {file = "rapidfuzz-1.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ebc0d3d15ed32f98f0052cf6e3e9c9b8010fb93c04fb74d2022e3c51ec540e2"}, + {file = "rapidfuzz-1.9.1-cp38-cp38-win32.whl", hash = "sha256:477ab1a3044bab89db45caabc562b158f68765ecaa638b73ba17e92f09dfa5ff"}, + {file = "rapidfuzz-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:8e872763dc0367d7544aa585d2e8b27af233323b8a7cd2f9b78cafa05bae5018"}, + {file = "rapidfuzz-1.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8401c41e219ae36ca7a88762776a6270511650d4cc70d024ae61561e96d67e47"}, + {file = "rapidfuzz-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ea10bd8e0436801c3264f7084a5ea194f12ba9fe1ba898aa4a2107d276501292"}, + {file = "rapidfuzz-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:433737914b46c1ffa0c678eceae1c260dc6b7fb5b6cad4c725d3e3607c764b32"}, + {file = "rapidfuzz-1.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8c3b08e90e45acbc469d1f456681643256e952bf84ec7714f58979baba0c8a1c"}, + {file = "rapidfuzz-1.9.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bbcd265b3c86176e5db4cbba7b4364d7333c214ee80e2d259c7085929934ca9d"}, + {file = "rapidfuzz-1.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d69fabcd635783cd842e7d5ee4b77164314c5124b82df5a0c436ab3d698f8a9"}, + {file = "rapidfuzz-1.9.1-cp39-cp39-win32.whl", hash = "sha256:01f16b6f3fa5d1a26c12f5da5de0032f1e12c919d876005b57492a8ec9a5c043"}, + {file = "rapidfuzz-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:0bcc5bbfdbe6068cc2cf0029ab6cde08dceac498d232fa3a61dd34fbfa0b3f36"}, + {file = "rapidfuzz-1.9.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:de869c8f4e8edb9b2f7b8232a04896645501defcbd9d85bc0202ff3ec6285f6b"}, + {file = "rapidfuzz-1.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:db5978e970fb0955974d51021da4b929e2e4890fef17792989ee32658e2b159c"}, + {file = "rapidfuzz-1.9.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:33479f75f36ac3a1d8421365d4fa906e013490790730a89caba31d06e6f71738"}, + {file = "rapidfuzz-1.9.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:af991cb333ec526d894923163050931b3a870b7694bf7687aaa6154d341a98f5"}, + {file = "rapidfuzz-1.9.1.tar.gz", hash = "sha256:bd7a4fe33ba49db3417f0f57a8af02462554f1296dedcf35b026cd3525efef74"}, ] redis = [ - {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, - {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, + {file = "redis-4.0.2-py3-none-any.whl", hash = "sha256:c8481cf414474e3497ec7971a1ba9b998c8efad0f0d289a009a5bbef040894f9"}, + {file = "redis-4.0.2.tar.gz", hash = "sha256:ccf692811f2c1fc7a92b466aa2599e4a6d2d73d5f736a2c70be600657c0da34a"}, ] sentry-sdk = [ {file = "sentry-sdk-0.20.3.tar.gz", hash = "sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237"}, @@ -1618,16 +1487,16 @@ six = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] snowballstemmer = [ - {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, - {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] sortedcontainers = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] soupsieve = [ - {file = "soupsieve-2.3-py3-none-any.whl", hash = "sha256:617ffc4d0dfd39c66f4d1413a6e165663a34eca86be9b54f97b91756300ff6df"}, - {file = "soupsieve-2.3.tar.gz", hash = "sha256:e4860f889dfa88774c07da0b276b70c073b6470fa1a4a8350800bb7bce3dcc76"}, + {file = "soupsieve-2.3.1-py3-none-any.whl", hash = "sha256:1a3cca2617c6b38c0343ed661b1fa5de5637f257d4fe22bd9f1338010a1efefb"}, + {file = "soupsieve-2.3.1.tar.gz", hash = "sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9"}, ] taskipy = [ {file = "taskipy-1.9.0-py3-none-any.whl", hash = "sha256:02bd2c51c7356ed3f7f8853210ada1cd2ab273e68359ee865021c3057eec6615"}, @@ -1642,17 +1511,69 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] urllib3 = [ {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, ] virtualenv = [ - {file = "virtualenv-20.10.0-py2.py3-none-any.whl", hash = "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814"}, - {file = "virtualenv-20.10.0.tar.gz", hash = "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218"}, + {file = "virtualenv-20.11.0-py2.py3-none-any.whl", hash = "sha256:eb0cb34160f32c6596405308ee6a8a4abbf3247b2b9794ae655a156d43abf48e"}, + {file = "virtualenv-20.11.0.tar.gz", hash = "sha256:2f15b9226cb74b59c21e8236dd791c395bee08cdd33b99cddd18e1f866cdb098"}, +] +wrapt = [ + {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, + {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, + {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, + {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, + {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, + {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, + {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, + {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, + {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, + {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, + {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, + {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, + {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, + {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, + {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, + {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, + {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, + {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, + {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, ] yarl = [ {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, diff --git a/pyproject.toml b/pyproject.toml index d758385e..2a216209 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,6 @@ sentry-sdk = "~=0.19" PyYAML = "~=5.4" async-rediscache = {extras = ["fakeredis"], version = "~=0.1.4"} emojis = "~=0.6.0" -matplotlib = "~=3.4.1" coloredlogs = "~=15.0" colorama = { version = "~=0.4.3", markers = "sys_platform == 'win32'" } lxml = "~=4.6" -- cgit v1.2.3 From 97f83b0c2e5b98cab9b967310396c56071a9f9f2 Mon Sep 17 00:00:00 2001 From: bones <35849006+Sn4u@users.noreply.github.com> Date: Sat, 8 Jan 2022 21:00:15 +0000 Subject: Merge Epoch Command (PR #983) Add timestamp converter command. Co-authored-by: Sn4u Co-authored-by: mathstrains21 <89940630+mathstrains21@users.noreply.github.com> Co-authored-by: mathstrains21 --- bot/exts/utilities/epoch.py | 135 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 bot/exts/utilities/epoch.py (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/epoch.py b/bot/exts/utilities/epoch.py new file mode 100644 index 00000000..b9feed18 --- /dev/null +++ b/bot/exts/utilities/epoch.py @@ -0,0 +1,135 @@ +from typing import Optional, Union + +import arrow +import discord +from dateutil import parser +from discord.ext import commands + +from bot.bot import Bot +from bot.utils.extensions import invoke_help_command + +# https://discord.com/developers/docs/reference#message-formatting-timestamp-styles +STYLES = { + "Epoch": ("",), + "Short Time": ("t", "h:mm A",), + "Long Time": ("T", "h:mm:ss A"), + "Short Date": ("d", "MM/DD/YYYY"), + "Long Date": ("D", "MMMM D, YYYY"), + "Short Date/Time": ("f", "MMMM D, YYYY h:mm A"), + "Long Date/Time": ("F", "dddd, MMMM D, YYYY h:mm A"), + "Relative Time": ("R",) +} +DROPDOWN_TIMEOUT = 60 + + +class DateString(commands.Converter): + """Convert a relative or absolute date/time string to an arrow.Arrow object.""" + + async def convert(self, ctx: commands.Context, argument: str) -> Union[arrow.Arrow, Optional[tuple]]: + """ + Convert a relative or absolute date/time string to an arrow.Arrow object. + + Try to interpret the date string as a relative time. If conversion fails, try to interpret it as an absolute + time. Tokens that are not recognised are returned along with the part of the string that was successfully + converted to an arrow object. If the date string cannot be parsed, BadArgument is raised. + """ + try: + return arrow.utcnow().dehumanize(argument) + except ValueError: + try: + dt, ignored_tokens = parser.parse(argument, fuzzy_with_tokens=True) + except parser.ParserError: + raise commands.BadArgument(f"`{argument}` Could not be parsed to a relative or absolute date.") + except OverflowError: + raise commands.BadArgument(f"`{argument}` Results in a date outside of the supported range.") + return arrow.get(dt), ignored_tokens + + +class Epoch(commands.Cog): + """Convert an entered time and date to a unix timestamp.""" + + @commands.command(name="epoch") + async def epoch(self, ctx: commands.Context, *, date_time: DateString = None) -> None: + """ + Convert an entered date/time string to the equivalent epoch. + + **Relative time** + Must begin with `in...` or end with `...ago`. + Accepted units: "seconds", "minutes", "hours", "days", "weeks", "months", "years". + eg `.epoch in a month 4 days and 2 hours` + + **Absolute time** + eg `.epoch 2022/6/15 16:43 -04:00` + Absolute times must be entered in descending orders of magnitude. + If AM or PM is left unspecified, the 24-hour clock is assumed. + Timezones are optional, and will default to UTC. The following timezone formats are accepted: + Z (UTC) + ±HH:MM + ±HHMM + ±HH + + Times in the dropdown are shown in UTC + """ + if not date_time: + await invoke_help_command(ctx) + return + + if isinstance(date_time, tuple): + # Remove empty strings. Strip extra whitespace from the remaining items + ignored_tokens = list(map(str.strip, filter(str.strip, date_time[1]))) + date_time = date_time[0] + if ignored_tokens: + await ctx.send(f"Could not parse the following token(s): `{', '.join(ignored_tokens)}`") + await ctx.send(f"Date and time parsed as: `{date_time.format(arrow.FORMAT_RSS)}`") + + epoch = int(date_time.timestamp()) + view = TimestampMenuView(ctx, self._format_dates(date_time), epoch) + original = await ctx.send(f"`{epoch}`", view=view) + await view.wait() # wait until expiration before removing the dropdown + await original.edit(view=None) + + @staticmethod + def _format_dates(date: arrow.Arrow) -> list[str]: + """ + Return a list of date strings formatted according to the discord timestamp styles. + + These are used in the description of each style in the dropdown + """ + date = date.to('utc') + formatted = [str(int(date.timestamp()))] + formatted += [date.format(format[1]) for format in list(STYLES.values())[1:7]] + formatted.append(date.humanize()) + return formatted + + +class TimestampMenuView(discord.ui.View): + """View for the epoch command which contains a single `discord.ui.Select` dropdown component.""" + + def __init__(self, ctx: commands.Context, formatted_times: list[str], epoch: int): + super().__init__(timeout=DROPDOWN_TIMEOUT) + self.ctx = ctx + self.epoch = epoch + self.dropdown: discord.ui.Select = self.children[0] + for label, date_time in zip(STYLES.keys(), formatted_times): + self.dropdown.add_option(label=label, description=date_time) + + @discord.ui.select(placeholder="Select the format of your timestamp") + async def select_format(self, _: discord.ui.Select, interaction: discord.Interaction) -> discord.Message: + """Drop down menu which contains a list of formats which discord timestamps can take.""" + selected = interaction.data["values"][0] + if selected == "Epoch": + return await interaction.response.edit_message(content=f"`{self.epoch}`") + return await interaction.response.edit_message(content=f"``") + + async def interaction_check(self, interaction: discord.Interaction) -> bool: + """Check to ensure that the interacting user is the user who invoked the command.""" + if interaction.user != self.ctx.author: + embed = discord.Embed(description="Sorry, but this dropdown menu can only be used by the original author.") + await interaction.response.send_message(embed=embed, ephemeral=True) + return False + return True + + +def setup(bot: Bot) -> None: + """Load the Epoch cog.""" + bot.add_cog(Epoch()) -- cgit v1.2.3 From 6387fa042c4bbd31ccb04c3820e9264f3281b98f Mon Sep 17 00:00:00 2001 From: Sn4u <35849006+Sn4u@users.noreply.github.com> Date: Sat, 8 Jan 2022 22:11:24 +0000 Subject: handle OverflowError in relative times converter --- bot/exts/utilities/epoch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/epoch.py b/bot/exts/utilities/epoch.py index b9feed18..03758af0 100644 --- a/bot/exts/utilities/epoch.py +++ b/bot/exts/utilities/epoch.py @@ -35,7 +35,7 @@ class DateString(commands.Converter): """ try: return arrow.utcnow().dehumanize(argument) - except ValueError: + except (ValueError, OverflowError): try: dt, ignored_tokens = parser.parse(argument, fuzzy_with_tokens=True) except parser.ParserError: -- cgit v1.2.3 From c815a19612be9e0a28403786696964e14420204f Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Mon, 14 Feb 2022 18:26:10 -0500 Subject: fix: Add newlines in codeblock formatting --- bot/exts/utilities/githubinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/githubinfo.py b/bot/exts/utilities/githubinfo.py index 009e0fad..963f54e5 100644 --- a/bot/exts/utilities/githubinfo.py +++ b/bot/exts/utilities/githubinfo.py @@ -257,7 +257,7 @@ class GithubInfo(commands.Cog): embed = discord.Embed( title=f"`{user_data['login']}`'s GitHub profile info", - description=f"```{user_data['bio']}```\n" if user_data["bio"] else "", + description=f"```\n{user_data['bio']}\n```\n" if user_data["bio"] else "", colour=discord.Colour.og_blurple(), url=user_data["html_url"], timestamp=datetime.strptime(user_data["created_at"], "%Y-%m-%dT%H:%M:%SZ") -- cgit v1.2.3 From f4ffffb052552ea3271b1c7d533d127d583023e4 Mon Sep 17 00:00:00 2001 From: MaskDuck Date: Wed, 16 Feb 2022 01:33:42 +0700 Subject: Fix #1024 (#1030) --- bot/exts/utilities/epoch.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'bot/exts/utilities') diff --git a/bot/exts/utilities/epoch.py b/bot/exts/utilities/epoch.py index 03758af0..42312dd1 100644 --- a/bot/exts/utilities/epoch.py +++ b/bot/exts/utilities/epoch.py @@ -86,7 +86,10 @@ class Epoch(commands.Cog): view = TimestampMenuView(ctx, self._format_dates(date_time), epoch) original = await ctx.send(f"`{epoch}`", view=view) await view.wait() # wait until expiration before removing the dropdown - await original.edit(view=None) + try: + await original.edit(view=None) + except discord.NotFound: # disregard the error message if the message is deleled + pass @staticmethod def _format_dates(date: arrow.Arrow) -> list[str]: -- cgit v1.2.3 From a468379dec50a69b1932516e3ed66ce0f157fd2b Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Mon, 14 Mar 2022 01:56:00 +0100 Subject: Add Twemoji utility command (#988) Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/constants.py | 1 + bot/exts/utilities/twemoji.py | 150 ++++++++++++++++++++++++++++++++++++++++++ poetry.lock | 16 ++++- pyproject.toml | 1 + 4 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 bot/exts/utilities/twemoji.py (limited to 'bot/exts/utilities') diff --git a/bot/constants.py b/bot/constants.py index 5d876d97..da81a089 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -157,6 +157,7 @@ class Logging(NamedTuple): class Colours: blue = 0x0279FD + twitter_blue = 0x1DA1F2 bright_green = 0x01D277 dark_green = 0x1F8B4C orange = 0xE67E22 diff --git a/bot/exts/utilities/twemoji.py b/bot/exts/utilities/twemoji.py new file mode 100644 index 00000000..c915f05b --- /dev/null +++ b/bot/exts/utilities/twemoji.py @@ -0,0 +1,150 @@ +import logging +import re +from typing import Literal, Optional + +import discord +from discord.ext import commands +from emoji import UNICODE_EMOJI_ENGLISH, is_emoji + +from bot.bot import Bot +from bot.constants import Colours, Roles +from bot.utils.decorators import whitelist_override +from bot.utils.extensions import invoke_help_command + +log = logging.getLogger(__name__) +BASE_URLS = { + "png": "https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/", + "svg": "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/", +} +CODEPOINT_REGEX = re.compile(r"[a-f1-9][a-f0-9]{3,5}$") + + +class Twemoji(commands.Cog): + """Utilities for working with Twemojis.""" + + def __init__(self, bot: Bot): + self.bot = bot + + @staticmethod + def get_url(codepoint: str, format: Literal["png", "svg"]) -> str: + """Returns a source file URL for the specified Twemoji, in the corresponding format.""" + return f"{BASE_URLS[format]}{codepoint}.{format}" + + @staticmethod + def alias_to_name(alias: str) -> str: + """ + Transform a unicode alias to an emoji name. + + Example usages: + >>> alias_to_name(":falling_leaf:") + "Falling leaf" + >>> alias_to_name(":family_man_girl_boy:") + "Family man girl boy" + """ + name = alias.strip(":").replace("_", " ") + return name.capitalize() + + @staticmethod + def build_embed(codepoint: str) -> discord.Embed: + """Returns the main embed for the `twemoji` commmand.""" + emoji = "".join(Twemoji.emoji(e) or "" for e in codepoint.split("-")) + + embed = discord.Embed( + title=Twemoji.alias_to_name(UNICODE_EMOJI_ENGLISH[emoji]), + description=f"{codepoint.replace('-', ' ')}\n[Download svg]({Twemoji.get_url(codepoint, 'svg')})", + colour=Colours.twitter_blue, + ) + embed.set_thumbnail(url=Twemoji.get_url(codepoint, "png")) + return embed + + @staticmethod + def emoji(codepoint: Optional[str]) -> Optional[str]: + """ + Returns the emoji corresponding to a given `codepoint`, or `None` if no emoji was found. + + The return value is an emoji character, such as "🍂". The `codepoint` + argument can be of any format, since it will be trimmed automatically. + """ + if code := Twemoji.trim_code(codepoint): + return chr(int(code, 16)) + + @staticmethod + def codepoint(emoji: Optional[str]) -> Optional[str]: + """ + Returns the codepoint, in a trimmed format, of a single emoji. + + `emoji` should be an emoji character, such as "🐍" and "🥰", and + not a codepoint like "1f1f8". When working with combined emojis, + such as "🇸🇪" and "👨‍👩‍👦", send the component emojis through the method + one at a time. + """ + if emoji is None: + return None + return hex(ord(emoji)).removeprefix("0x") + + @staticmethod + def trim_code(codepoint: Optional[str]) -> Optional[str]: + """ + Returns the meaningful information from the given `codepoint`. + + If no codepoint is found, `None` is returned. + + Example usages: + >>> trim_code("U+1f1f8") + "1f1f8" + >>> trim_code("\u0001f1f8") + "1f1f8" + >>> trim_code("1f466") + "1f466" + """ + if code := CODEPOINT_REGEX.search(codepoint or ""): + return code.group() + + @staticmethod + def codepoint_from_input(raw_emoji: tuple[str, ...]) -> str: + """ + Returns the codepoint corresponding to the passed tuple, separated by "-". + + The return format matches the format used in URLs for Twemoji source files. + + Example usages: + >>> codepoint_from_input(("🐍",)) + "1f40d" + >>> codepoint_from_input(("1f1f8", "1f1ea")) + "1f1f8-1f1ea" + >>> codepoint_from_input(("👨‍👧‍👦",)) + "1f468-200d-1f467-200d-1f466" + """ + raw_emoji = [emoji.lower() for emoji in raw_emoji] + if is_emoji(raw_emoji[0]): + emojis = (Twemoji.codepoint(emoji) or "" for emoji in raw_emoji[0]) + return "-".join(emojis) + + emoji = "".join( + Twemoji.emoji(Twemoji.trim_code(code)) or "" for code in raw_emoji + ) + if is_emoji(emoji): + return "-".join(Twemoji.codepoint(e) or "" for e in emoji) + + raise ValueError("No codepoint could be obtained from the given input") + + @commands.command(aliases=("tw",)) + @whitelist_override(roles=(Roles.everyone,)) + async def twemoji(self, ctx: commands.Context, *raw_emoji: str) -> None: + """Sends a preview of a given Twemoji, specified by codepoint or emoji.""" + if len(raw_emoji) == 0: + await invoke_help_command(ctx) + return + try: + codepoint = self.codepoint_from_input(raw_emoji) + except ValueError: + raise commands.BadArgument( + "please include a valid emoji or emoji codepoint." + ) + + await ctx.send(embed=self.build_embed(codepoint)) + + +def setup(bot: Bot) -> None: + """Load the Twemoji cog.""" + bot.add_cog(Twemoji(bot)) diff --git a/poetry.lock b/poetry.lock index 68bfc43a..56eb53d9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -202,6 +202,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "emoji" +version = "1.6.3" +description = "Emoji for Python" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +dev = ["pytest", "coverage", "coveralls"] + [[package]] name = "emojis" version = "0.6.0" @@ -824,7 +835,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "e824a5fa909d43e861478178ad7e77ee04be05a60cd3028bda8bd4754c848616" +content-hash = "27075494e06333e5934e751f9847f419efb712b6d4d4e6173785d47319de1f29" [metadata.files] aiodns = [ @@ -975,6 +986,9 @@ distlib = [ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] +emoji = [ + {file = "emoji-1.6.3.tar.gz", hash = "sha256:cc28bdc1010b1c03c241f69c7af1e8715144ef45a273bfadc14dc89319ba26d0"}, +] emojis = [ {file = "emojis-0.6.0-py3-none-any.whl", hash = "sha256:7da34c8a78ae262fd68cef9e2c78a3c1feb59784489eeea0f54ba1d4b7111c7c"}, {file = "emojis-0.6.0.tar.gz", hash = "sha256:bf605d1f1a27a81cd37fe82eb65781c904467f569295a541c33710b97e4225ec"}, diff --git a/pyproject.toml b/pyproject.toml index 7d3f0a5e..a72fa706 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ emojis = "~=0.6.0" coloredlogs = "~=15.0" colorama = { version = "~=0.4.3", markers = "sys_platform == 'win32'" } lxml = "~=4.6" +emoji = "^1.6.1" [tool.poetry.dev-dependencies] flake8 = "~=3.8" -- cgit v1.2.3