diff options
| author | 2021-10-24 18:26:45 -0400 | |
|---|---|---|
| committer | 2021-10-24 18:26:45 -0400 | |
| commit | 46620dd929b8359af1c086b3e9b6e23633398f6e (patch) | |
| tree | 84a2b78c23709b5a03d10a72e9e2f244a82e5dbb /bot/exts/utilities | |
| parent | temp: add restructured template as comments (diff) | |
| parent | chore: remove single-use constant for json path (diff) | |
fix: merge conflicts
Merge branch 'color-677' of github.com:brad90four/sir-lancebot into color-677
Diffstat (limited to 'bot/exts/utilities')
| -rw-r--r-- | bot/exts/utilities/bookmark.py | 13 | ||||
| -rw-r--r-- | bot/exts/utilities/color.py | 471 | ||||
| -rw-r--r-- | bot/exts/utilities/emoji.py | 4 |
3 files changed, 11 insertions, 477 deletions
diff --git a/bot/exts/utilities/bookmark.py b/bot/exts/utilities/bookmark.py index a91ef1c0..a11c366b 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, @@ -99,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 diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py deleted file mode 100644 index c1523281..00000000 --- a/bot/exts/utilities/color.py +++ /dev/null @@ -1,471 +0,0 @@ -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)) 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(), |