Skip to content

Commit

Permalink
[Palette] Hotfix (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kuro-Rui authored Jul 21, 2024
1 parent d010f5b commit 8760cb6
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 108 deletions.
42 changes: 20 additions & 22 deletions palette/converters.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# Taken and modified from Trustys NotSoBot cog.
# Taken and modified from Trusty's NotSoBot cog.
import re

import discord
from redbot.core.commands import BadArgument, Converter

_id_regex = re.compile(r"([0-9]{15,21})$")
_mention_regex = re.compile(r"<@!?([0-9]{15,21})>$")

IMAGE_LINKS = re.compile(r"(https?:\/\/[^\"\'\s]*\.(?:png|jpg|jpeg|gif|png|svg)(\?size=[0-9]*)?)")
IMAGE_LINKS = re.compile(r"(https?:\/\/[^\"\'\s]*\.(?:png|jpg|jpeg|gif)(\?size=[0-9]*)?)")
EMOJI_REGEX = re.compile(r"(<(a)?:[a-zA-Z0-9\_]+:([0-9]+)>)")
MENTION_REGEX = re.compile(r"<@!?([0-9]+)>")
ID_REGEX = re.compile(r"[0-9]{17,}")
Expand All @@ -17,38 +15,38 @@ class ImageFinder(Converter):
converter class."""

async def convert(self, ctx, argument):
attachments = ctx.message.attachments
mentions = MENTION_REGEX.finditer(argument)
matches = IMAGE_LINKS.finditer(argument)
emojis = EMOJI_REGEX.finditer(argument)
ids = ID_REGEX.finditer(argument)
urls = []
if matches:
urls.extend(match.group(1) for match in matches)
# If it's invalid it will be handled by Palette.get_img later
urls.extend(match.string for match in matches)
if emojis:
for emoji in emojis:
ext = "gif" if emoji.group(2) else "png"
url = "https://cdn.discordapp.com/emojis/{id}.{ext}?v=1".format(
id=emoji.group(3), ext=ext
)
urls.append(url)
partial_emoji = discord.PartialEmoji.from_str(emoji.group(1))
if partial_emoji.is_custom_emoji():
urls.append(partial_emoji.url)
# else:
# # Default emoji
# try:
# """https://github.com/glasnt/emojificate/blob/master/emojificate/filter.py"""
# cdn_fmt = "https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/{codepoint:x}.png"
# urls.append(cdn_fmt.format(codepoint=ord(str(emoji))))
# except TypeError:
# continue
if mentions:
for mention in mentions:
user = ctx.guild.get_member(int(mention.group(1)))
if user is not None:
url = IMAGE_LINKS.search(str(user.display_avatar))
urls.append(url.group(1))
if user := ctx.guild.get_member(int(mention.group(1))):
urls.append(str(user.display_avatar))
if not urls and ids:
for possible_id in ids:
if user := ctx.guild.get_member(int(possible_id.group(0))):
url = IMAGE_LINKS.search(str(user.display_avatar))
urls.append(url.group(1))
if attachments:
urls.extend(attachment.url for attachment in attachments)
urls.append(str(user.display_avatar))
if not urls and ctx.guild:
if user := ctx.guild.get_member_named(argument):
url = user.display_avatar
urls.append(url)
urls.append(str(user.display_avatar))
if not urls:
raise BadArgument("No images found.")
return urls[0]
8 changes: 5 additions & 3 deletions palette/info.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"author": [
"flare(flare#0001)"
"flare(flare#0001)",
"Kuro"
],
"install_msg": "Thanks for this installing the Palette cog. This cog comes bundled with a font file.",
"name": "Palette",
"disabled": true,
"disabled": false,
"short": "Show colour palette of images, avatars etc.",
"description": "Show colour palette of images, avatars, emojis etc",
"tags": [
Expand All @@ -18,7 +19,8 @@
],
"requirements": [
"pillow",
"colorgram.py"
"colorgram.py==1.2.0",
"tabulate"
],
"min_bot_version": "3.5.0"
}
174 changes: 95 additions & 79 deletions palette/palette.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import asyncio
from io import BytesIO
from typing import Optional

import aiohttp
import colorgram
import discord
from PIL import Image, ImageDraw, ImageFile, ImageFont
from PIL import Image, ImageDraw, ImageFont
from redbot.core import commands
from redbot.core.data_manager import bundled_data_path
from redbot.core.utils.chat_formatting import box
from tabulate import tabulate

from .converters import ImageFinder

VALID_CONTENT_TYPES = ("image/png", "image/jpeg", "image/jpg", "image/gif")


class Palette(commands.Cog):
"""
This is a collection of commands that are used to show colour palettes.
"""

__version__ = "0.0.2"
__author__ = "flare(flare#0001)"
__version__ = "0.1.0"
__author__ = "flare(flare#0001) and Kuro"

def format_help_for_context(self, ctx):
pre_processed = super().format_help_for_context(ctx)
Expand All @@ -28,103 +31,116 @@ def __init__(self, bot):
self.bot = bot
self.session = aiohttp.ClientSession()

def cog_unload(self):
asyncio.create_task(self.session.close())
async def cog_unload(self):
await self.session.close()

def rgb_to_hex(self, rgb):
return "%02x%02x%02x" % rgb

async def get_img(self, ctx, url):
async with ctx.typing():
async with self.session.get(url) as resp:
if resp.status in [200, 201]:
file = await resp.read()
file = BytesIO(file)
file.seek(0)
return file
if resp.status == 404:
return {
"error": "Server not found, ensure the correct URL is setup and is reachable. "
}
return {"error": resp.status}
return "#%02x%02x%02x" % rgb

async def get_img(self, url):
async with self.session.get(url) as resp:
if resp.status == 404:
return {
"error": "Server not found, ensure the correct URL is setup and is reachable. "
}
if resp.headers.get("Content-Type") not in VALID_CONTENT_TYPES:
return {"error": "Invalid image."}
if resp.status in [200, 201]:
file = await resp.read()
file = BytesIO(file)
file.seek(0)
return file
return {"error": resp.status}

@commands.command()
async def palette(
self,
ctx,
img: Optional[ImageFinder] = None,
amount: Optional[int] = 10,
sorted: bool = False,
):
"""Colour palette of an image
By default it is sorted by prominence, but you can sort it by rgb by passing true."""
if amount < 1:
return await ctx.send("Colours should be at least 1.")
if amount > 50:
return await ctx.send("Too many colours, please limit to 50.")
if img is None:
img = str(ctx.author.display_avatar)
async with ctx.typing():
img = await self.get_img(ctx, str(img))
if isinstance(img, dict):
return await ctx.send(img["error"])
image = await self.create_palette(img, amount, False, sorted)
await ctx.send(file=image)

@commands.command()
async def hexpalette(
self,
ctx,
img: Optional[ImageFinder] = None,
amount: Optional[int] = 10,
sorted: bool = False,
image: Optional[ImageFinder] = None,
amount: Optional[commands.Range[int, 1, 50]] = 10,
detailed: Optional[bool] = False,
sort: Optional[bool] = False,
):
"""Colour palette of an image with hex values
By default it is sorted by prominence, but you can sort it by rgb by passing true."""
if amount < 1:
return await ctx.send("Colours should be at least 1.")
if amount > 50:
return await ctx.send("Too many colours, please limit to 50.")
if img is None:
img = str(ctx.author.display_avatar)
"""Get the colour palette of an image.
**Arguments**
- `[image]` The image to get the palette from. If not provided, the author's avatar will be used. You can also provide an attachment.
- `[amount]` The amount of colours to get. Must be between 1 and 50. Defaults to 10.
- `[detailed]` Whether to show the colours in a detailed format (with rgb and hex). Defaults to False.
- `[sort]` Whether to sort the colours by rgb. Defaults to False.
"""
if not image:
image = str(ctx.author.display_avatar)
if attachments := ctx.message.attachments:
valid_attachments = [
a for a in attachments if a.content_type in VALID_CONTENT_TYPES
]
if valid_attachments:
image = valid_attachments[0].url
async with ctx.typing():
img = await self.get_img(ctx, str(img))
img = await self.get_img(image)
if isinstance(img, dict):
return await ctx.send(img["error"])
image = await self.create_palette(img, amount, True, sorted)
await ctx.send(file=image)

async def create_palette(self, img: BytesIO, amount: int, show_hex: bool, sorted: bool):
colors = colorgram.extract(img, amount)
if sorted:
colors, file = await self.bot.loop.run_in_executor(
None, self.create_palette, img, amount, detailed, sort
)
if not detailed:
return await ctx.send(file=file)

table = []
for i, color in enumerate(colors, start=1):
row = [f"{color.rgb.r}, {color.rgb.g}, {color.rgb.b}", self.rgb_to_hex(color.rgb)]
if len(colors) > 1:
row.insert(0, str(i))
table.append(row)
headers = ["#", "RGB", "Hex"] if len(colors) > 1 else ["RGB", "Hex"]

embed = discord.Embed(
color=await ctx.embed_color(),
title="Colour Palette",
description=box(tabulate(table, headers), lang="css"),
)
embed.set_thumbnail(url=image)
embed.set_image(url=f"attachment://{file.filename}")
await ctx.send(
embed=embed,
file=file,
reference=ctx.message.to_reference(fail_if_not_exists=False),
mention_author=False,
)

def create_palette(self, fp: BytesIO, amount: int, detailed: bool, sort: bool):
colors = colorgram.extract(fp, amount)
if sort:
colors.sort(key=lambda c: c.rgb)

dimensions = (500 * len(colors), 500) if show_hex else (100 * len(colors), 100)
dimensions = (500 * len(colors), 500) if detailed else (100 * len(colors), 100)
final = Image.new("RGBA", dimensions)
a = ImageDraw.Draw(final)
start = 0
if show_hex:
if detailed:
font_file = f"{bundled_data_path(self)}/fonts/RobotoRegular.ttf"
name_fnt = ImageFont.truetype(font_file, 52, encoding="utf-8")
for color in colors:
name_fnt = ImageFont.truetype(font_file, 69, encoding="utf-8")
for i, color in enumerate(colors, start=1):
a.rectangle(
[(start, 0), (start + dimensions[1], 450 if show_hex else 100)], fill=color.rgb
[(start, 0), (start + dimensions[1], 431 if detailed else 100)], fill=color.rgb
)
if show_hex:
msg = f"#{self.rgb_to_hex(color.rgb)}"
a.text(
(start + dimensions[1] // 2, 500),
msg,
font=name_fnt,
fill=(255, 255, 255, 255),
anchor="mb",
)
if detailed:
# Bold text effect
offsets = ((0, 0), (1, 0), (0, 1), (1, 1))
for xo, yo in offsets:
a.text(
(start + dimensions[1] // 2 + xo, 499 + yo),
str(i),
fill=(255, 255, 255, 255),
font=name_fnt,
anchor="mb",
)
start = start + dimensions[1]
final = final.resize((500 * len(colors), 500), resample=Image.ANTIALIAS)
final = final.resize((500 * len(colors), 500), resample=Image.Resampling.LANCZOS)
fileObj = BytesIO()
final.save(fileObj, "png")
fileObj.name = "palette.png"
fileObj.seek(0)
return discord.File(fileObj)
return colors, discord.File(fileObj)
8 changes: 4 additions & 4 deletions tiktokreposter/tiktokreposter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@
if sys.version_info < (3, 9):
import asyncio
import contextvars
from typing import TypeVar, Callable
from typing import Callable, TypeVar

from typing_extensions import ParamSpec

T = TypeVar("T")
P = ParamSpec("P")

# backport of 3.9's asyncio.to_thread
async def to_thread(
func: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs
) -> T:
async def to_thread(func: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
loop = asyncio.get_running_loop()
ctx = contextvars.copy_context()
func_call = functools.partial(ctx.run, func, *args, **kwargs)
return await loop.run_in_executor(None, func_call) # type: ignore

else:
from asyncio import to_thread

Expand Down

0 comments on commit 8760cb6

Please sign in to comment.