aboutsummaryrefslogtreecommitdiffstats
path: root/bot/exts/utilities
diff options
context:
space:
mode:
authorGravatar bradtimmis <[email protected]>2021-10-24 18:26:45 -0400
committerGravatar bradtimmis <[email protected]>2021-10-24 18:26:45 -0400
commit46620dd929b8359af1c086b3e9b6e23633398f6e (patch)
tree84a2b78c23709b5a03d10a72e9e2f244a82e5dbb /bot/exts/utilities
parenttemp: add restructured template as comments (diff)
parentchore: 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.py13
-rw-r--r--bot/exts/utilities/color.py471
-rw-r--r--bot/exts/utilities/emoji.py4
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(),