aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/exts/easter/avatar_easterifier.py128
-rw-r--r--bot/exts/evergreen/8bitify.py54
-rw-r--r--bot/exts/evergreen/pfp_modify.py337
-rw-r--r--bot/exts/halloween/spookyavatar.py52
-rw-r--r--bot/exts/pride/pride_avatar.py177
5 files changed, 337 insertions, 411 deletions
diff --git a/bot/exts/easter/avatar_easterifier.py b/bot/exts/easter/avatar_easterifier.py
deleted file mode 100644
index 8e8a3500..00000000
--- a/bot/exts/easter/avatar_easterifier.py
+++ /dev/null
@@ -1,128 +0,0 @@
-import asyncio
-import logging
-from io import BytesIO
-from pathlib import Path
-from typing import Tuple, Union
-
-import discord
-from PIL import Image
-from PIL.ImageOps import posterize
-from discord.ext import commands
-
-log = logging.getLogger(__name__)
-
-COLOURS = [
- (255, 247, 0), (255, 255, 224), (0, 255, 127), (189, 252, 201), (255, 192, 203),
- (255, 160, 122), (181, 115, 220), (221, 160, 221), (200, 162, 200), (238, 130, 238),
- (135, 206, 235), (0, 204, 204), (64, 224, 208)
-] # Pastel colours - Easter-like
-
-
-class AvatarEasterifier(commands.Cog):
- """Put an Easter spin on your avatar or image!"""
-
- def __init__(self, bot: commands.Bot):
- self.bot = bot
-
- @staticmethod
- def closest(x: Tuple[int, int, int]) -> Tuple[int, int, int]:
- """
- Finds the closest easter colour to a given pixel.
-
- Returns a merge between the original colour and the closest colour
- """
- r1, g1, b1 = x
-
- def distance(point: Tuple[int, int, int]) -> Tuple[int, int, int]:
- """Finds the difference between a pastel colour and the original pixel colour."""
- r2, g2, b2 = point
- return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2)
-
- closest_colours = sorted(COLOURS, key=lambda point: distance(point))
- r2, g2, b2 = closest_colours[0]
- r = (r1 + r2) // 2
- g = (g1 + g2) // 2
- b = (b1 + b2) // 2
-
- return (r, g, b)
-
- @commands.command(pass_context=True, aliases=["easterify"])
- async def avatareasterify(self, ctx: commands.Context, *colours: Union[discord.Colour, str]) -> None:
- """
- This "Easterifies" the user's avatar.
-
- Given colours will produce a personalised egg in the corner, similar to the egg_decorate command.
- If colours are not given, a nice little chocolate bunny will sit in the corner.
- Colours are split by spaces, unless you wrap the colour name in double quotes.
- Discord colour names, HTML colour names, XKCD colour names and hex values are accepted.
- """
- async def send(*args, **kwargs) -> str:
- """
- This replaces the original ctx.send.
-
- When invoking the egg decorating command, the egg itself doesn't print to to the channel.
- Returns the message content so that if any errors occur, the error message can be output.
- """
- if args:
- return args[0]
-
- async with ctx.typing():
-
- # Grabs image of avatar
- image_bytes = await ctx.author.avatar_url_as(size=256).read()
-
- old = Image.open(BytesIO(image_bytes))
- old = old.convert("RGBA")
-
- # Grabs alpha channel since posterize can't be used with an RGBA image.
- alpha = old.getchannel("A").getdata()
- old = old.convert("RGB")
- old = posterize(old, 6)
-
- data = old.getdata()
- setted_data = set(data)
- new_d = {}
-
- for x in setted_data:
- new_d[x] = self.closest(x)
- await asyncio.sleep(0) # Ensures discord doesn't break in the background.
- new_data = [(*new_d[x], alpha[i]) if x in new_d else x for i, x in enumerate(data)]
-
- im = Image.new("RGBA", old.size)
- im.putdata(new_data)
-
- if colours:
- send_message = ctx.send
- ctx.send = send # Assigns ctx.send to a fake send
- egg = await ctx.invoke(self.bot.get_command("eggdecorate"), *colours)
- if isinstance(egg, str): # When an error message occurs in eggdecorate.
- return await send_message(egg)
-
- ratio = 64 / egg.height
- egg = egg.resize((round(egg.width * ratio), round(egg.height * ratio)))
- egg = egg.convert("RGBA")
- im.alpha_composite(egg, (im.width - egg.width, (im.height - egg.height)//2)) # Right centre.
- ctx.send = send_message # Reassigns ctx.send
- else:
- bunny = Image.open(Path("bot/resources/easter/chocolate_bunny.png"))
- im.alpha_composite(bunny, (im.width - bunny.width, (im.height - bunny.height)//2)) # Right centre.
-
- bufferedio = BytesIO()
- im.save(bufferedio, format="PNG")
-
- bufferedio.seek(0)
-
- file = discord.File(bufferedio, filename="easterified_avatar.png") # Creates file to be used in embed
- embed = discord.Embed(
- name="Your Lovely Easterified Avatar",
- description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D"
- )
- embed.set_image(url="attachment://easterified_avatar.png")
- embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url)
-
- await ctx.send(file=file, embed=embed)
-
-
-def setup(bot: commands.Bot) -> None:
- """Avatar Easterifier Cog load."""
- bot.add_cog(AvatarEasterifier(bot))
diff --git a/bot/exts/evergreen/8bitify.py b/bot/exts/evergreen/8bitify.py
deleted file mode 100644
index 54e68f80..00000000
--- a/bot/exts/evergreen/8bitify.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from io import BytesIO
-
-import discord
-from PIL import Image
-from discord.ext import commands
-
-
-class EightBitify(commands.Cog):
- """Make your avatar 8bit!"""
-
- def __init__(self, bot: commands.Bot) -> None:
- self.bot = bot
-
- @staticmethod
- def pixelate(image: Image) -> Image:
- """Takes an image and pixelates it."""
- return image.resize((32, 32), resample=Image.NEAREST).resize((1024, 1024), resample=Image.NEAREST)
-
- @staticmethod
- def quantize(image: Image) -> Image:
- """Reduces colour palette to 256 colours."""
- return image.quantize()
-
- @commands.command(name="8bitify")
- async def eightbit_command(self, ctx: commands.Context) -> None:
- """Pixelates your avatar and changes the palette to an 8bit one."""
- async with ctx.typing():
- image_bytes = await ctx.author.avatar_url.read()
- avatar = Image.open(BytesIO(image_bytes))
- avatar = avatar.convert("RGBA").resize((1024, 1024))
-
- eightbit = self.pixelate(avatar)
- eightbit = self.quantize(eightbit)
-
- bufferedio = BytesIO()
- eightbit.save(bufferedio, format="PNG")
- bufferedio.seek(0)
-
- file = discord.File(bufferedio, filename="8bitavatar.png")
-
- embed = discord.Embed(
- title="Your 8-bit avatar",
- description='Here is your avatar. I think it looks all cool and "retro"'
- )
-
- embed.set_image(url="attachment://8bitavatar.png")
- embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url)
-
- await ctx.send(file=file, embed=embed)
-
-
-def setup(bot: commands.Bot) -> None:
- """Cog load."""
- bot.add_cog(EightBitify(bot))
diff --git a/bot/exts/evergreen/pfp_modify.py b/bot/exts/evergreen/pfp_modify.py
new file mode 100644
index 00000000..f356d2f6
--- /dev/null
+++ b/bot/exts/evergreen/pfp_modify.py
@@ -0,0 +1,337 @@
+import asyncio
+import json
+import logging
+import os
+import typing as t
+from concurrent.futures import ThreadPoolExecutor
+from io import BytesIO
+from pathlib import Path
+
+import aiofiles
+import aiohttp
+import discord
+from PIL import Image, ImageDraw, ImageOps, UnidentifiedImageError
+from discord.ext import commands
+
+from bot.constants import Colours
+from bot.utils.halloween import spookifications
+
+log = logging.getLogger(__name__)
+
+EASTER_COLOURS = [
+ (255, 247, 0), (255, 255, 224), (0, 255, 127), (189, 252, 201), (255, 192, 203),
+ (255, 160, 122), (181, 115, 220), (221, 160, 221), (200, 162, 200), (238, 130, 238),
+ (135, 206, 235), (0, 204, 204), (64, 224, 208)
+] # Pastel colours - Easter-like
+
+_EXECUTOR = ThreadPoolExecutor(10)
+
+
+async def in_thread(func: t.Callable, *args) -> asyncio.Future:
+ """Allows non-async functions to work in async functions."""
+ loop = asyncio.get_event_loop()
+ return await loop.run_in_executor(_EXECUTOR, func, *args)
+
+
+class PfpModify(commands.Cog):
+ """Various commands for users to change their own profile picture."""
+
+ def __init__(self, bot: commands.Bot) -> None:
+ self.bot = bot
+ self.bot.loop.create_task(self.init_cog())
+
+ async def init_cog(self) -> None:
+ """Initial load from resources asynchronously."""
+ async with aiofiles.open('bot/resources/pride/gender_options.json') as f:
+ self.GENDER_OPTIONS = json.loads(await f.read())
+
+ @staticmethod
+ def closest(x: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]:
+ """
+ Finds the closest easter colour to a given pixel.
+
+ Returns a merge between the original colour and the closest colour
+ """
+ r1, g1, b1 = x
+
+ def distance(point: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]:
+ """Finds the difference between a pastel colour and the original pixel colour."""
+ r2, g2, b2 = point
+ return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2)
+
+ closest_colours = sorted(EASTER_COLOURS, key=lambda point: distance(point))
+ r2, g2, b2 = closest_colours[0]
+ r = (r1 + r2) // 2
+ g = (g1 + g2) // 2
+ b = (b1 + b2) // 2
+
+ return (r, g, b)
+
+ @staticmethod
+ def crop_avatar(avatar: Image) -> Image:
+ """This crops the avatar given into a circle."""
+ mask = Image.new("L", avatar.size, 0)
+ draw = ImageDraw.Draw(mask)
+ draw.ellipse((0, 0) + avatar.size, fill=255)
+ avatar.putalpha(mask)
+ return avatar
+
+ @staticmethod
+ def crop_ring(ring: Image, px: int) -> Image:
+ """This crops the given ring into a circle."""
+ mask = Image.new("L", ring.size, 0)
+ draw = ImageDraw.Draw(mask)
+ draw.ellipse((0, 0) + ring.size, fill=255)
+ draw.ellipse((px, px, 1024-px, 1024-px), fill=0)
+ ring.putalpha(mask)
+ return ring
+
+ def process_options(self, option: str, pixels: int) -> t.Tuple[str, int, str]:
+ """Does some shared preprocessing for the prideavatar commands."""
+ return option.lower(), max(0, min(512, pixels)), self.GENDER_OPTIONS.get(option)
+
+ def process_image(
+ self,
+ image_bytes: bytes,
+ pixels: int,
+ flag: str
+ ) -> discord.File:
+ """Constructs and returns the final image. Used by the pride commands."""
+ # This line can raise UnidentifiedImageError and must be handled by the calling func.
+ avatar = Image.open(BytesIO(image_bytes))
+ avatar = avatar.convert("RGBA").resize((1024, 1024))
+
+ avatar = self.crop_avatar(avatar)
+
+ ring = Image.open(Path(f"bot/resources/pride/flags/{flag}.png")).resize((1024, 1024))
+ ring = ring.convert("RGBA")
+ ring = self.crop_ring(ring, pixels)
+
+ avatar.alpha_composite(ring, (0, 0))
+ bufferedio = BytesIO()
+ avatar.save(bufferedio, format="PNG")
+ bufferedio.seek(0)
+
+ return discord.File(bufferedio, filename="pride_avatar.png") # Creates file to be used in embed
+
+ async def send_image(
+ self,
+ ctx: commands.Context,
+ image_bytes: bytes,
+ pixels: int,
+ flag: str,
+ option: str
+ ) -> None:
+ """Gets and sends the image in an embed. Used by the pride commands."""
+ try:
+ file = await in_thread(self.process_image, image_bytes, pixels, flag)
+ except UnidentifiedImageError:
+ ctx.send("Cannot identify image from provided URL")
+ return
+
+ embed = discord.Embed(
+ name="Your Lovely Pride Avatar",
+ description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D"
+ )
+ embed.set_image(url="attachment://pride_avatar.png")
+ embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url)
+ await ctx.send(file=file, embed=embed)
+
+ @commands.max_concurrency(1, commands.BucketType.guild, wait=True)
+ @commands.group()
+ async def pfp_modify(self, ctx: commands.Context) -> None:
+ """Groups all of the pfp modifing commands to allow a single concurrency limit."""
+ if not ctx.invoked_subcommand:
+ await ctx.send_help(ctx.command)
+
+ @pfp_modify.command(name="8bitify")
+ async def eightbit_command(self, ctx: commands.Context) -> None:
+ """Pixelates your avatar and changes the palette to an 8bit one."""
+ async with ctx.typing():
+ image_bytes = await ctx.author.avatar_url.read()
+ avatar = Image.open(BytesIO(image_bytes))
+ avatar = avatar.convert("RGBA").resize((1024, 1024))
+
+ # Pixilate and quantize
+ eightbit = avatar.resize((32, 32), resample=Image.NEAREST).resize((1024, 1024), resample=Image.NEAREST)
+ eightbit = eightbit.quantize()
+
+ bufferedio = BytesIO()
+ eightbit.save(bufferedio, format="PNG")
+ bufferedio.seek(0)
+
+ file = discord.File(bufferedio, filename="8bitavatar.png")
+
+ embed = discord.Embed(
+ title="Your 8-bit avatar",
+ description='Here is your avatar. I think it looks all cool and "retro"'
+ )
+
+ embed.set_image(url="attachment://8bitavatar.png")
+ embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url)
+
+ await ctx.send(file=file, embed=embed)
+
+ @pfp_modify.command(pass_context=True, aliases=["easterify"])
+ async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None:
+ """
+ This "Easterifies" the user's avatar.
+
+ Given colours will produce a personalised egg in the corner, similar to the egg_decorate command.
+ If colours are not given, a nice little chocolate bunny will sit in the corner.
+ Colours are split by spaces, unless you wrap the colour name in double quotes.
+ Discord colour names, HTML colour names, XKCD colour names and hex values are accepted.
+ """
+ async def send(*args, **kwargs) -> str:
+ """
+ This replaces the original ctx.send.
+
+ When invoking the egg decorating command, the egg itself doesn't print to to the channel.
+ Returns the message content so that if any errors occur, the error message can be output.
+ """
+ if args:
+ return args[0]
+
+ async with ctx.typing():
+
+ # Grabs image of avatar
+ image_bytes = await ctx.author.avatar_url_as(size=256).read()
+
+ old = Image.open(BytesIO(image_bytes))
+ old = old.convert("RGBA")
+
+ # Grabs alpha channel since posterize can't be used with an RGBA image.
+ alpha = old.getchannel("A").getdata()
+ old = old.convert("RGB")
+ old = ImageOps.posterize(old, 6)
+
+ data = old.getdata()
+ setted_data = set(data)
+ new_d = {}
+
+ for x in setted_data:
+ new_d[x] = self.closest(x)
+ await asyncio.sleep(0) # Ensures discord doesn't break in the background.
+ new_data = [(*new_d[x], alpha[i]) if x in new_d else x for i, x in enumerate(data)]
+
+ im = Image.new("RGBA", old.size)
+ im.putdata(new_data)
+
+ if colours:
+ send_message = ctx.send
+ ctx.send = send # Assigns ctx.send to a fake send
+ egg = await ctx.invoke(self.bot.get_command("eggdecorate"), *colours)
+ if isinstance(egg, str): # When an error message occurs in eggdecorate.
+ return await send_message(egg)
+
+ ratio = 64 / egg.height
+ egg = egg.resize((round(egg.width * ratio), round(egg.height * ratio)))
+ egg = egg.convert("RGBA")
+ im.alpha_composite(egg, (im.width - egg.width, (im.height - egg.height)//2)) # Right centre.
+ ctx.send = send_message # Reassigns ctx.send
+ else:
+ bunny = Image.open(Path("bot/resources/easter/chocolate_bunny.png"))
+ im.alpha_composite(bunny, (im.width - bunny.width, (im.height - bunny.height)//2)) # Right centre.
+
+ bufferedio = BytesIO()
+ im.save(bufferedio, format="PNG")
+
+ bufferedio.seek(0)
+
+ file = discord.File(bufferedio, filename="easterified_avatar.png") # Creates file to be used in embed
+ embed = discord.Embed(
+ name="Your Lovely Easterified Avatar",
+ description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D"
+ )
+ embed.set_image(url="attachment://easterified_avatar.png")
+ embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url)
+
+ await ctx.send(file=file, embed=embed)
+
+ @pfp_modify.group(aliases=["avatarpride", "pridepfp", "prideprofile"], invoke_without_command=True)
+ async def prideavatar(self, ctx: commands.Context, option: str = "lgbt", pixels: int = 64) -> None:
+ """
+ This surrounds an avatar with a border of a specified LGBT flag.
+
+ This defaults to the LGBT rainbow flag if none is given.
+ The amount of pixels can be given which determines the thickness of the flag border.
+ This has a maximum of 512px and defaults to a 64px border.
+ The full image is 1024x1024.
+ """
+ option, pixels, flag = self.process_options(option, pixels)
+ if flag is None:
+ return await ctx.send("I don't have that flag!")
+
+ async with ctx.typing():
+ image_bytes = await ctx.author.avatar_url.read()
+ await self.send_image(ctx, image_bytes, pixels, flag, option)
+
+ @prideavatar.command()
+ async def image(self, ctx: commands.Context, url: str, option: str = "lgbt", pixels: int = 64) -> None:
+ """
+ This surrounds the image specified by the URL with a border of a specified LGBT flag.
+
+ This defaults to the LGBT rainbow flag if none is given.
+ The amount of pixels can be given which determines the thickness of the flag border.
+ This has a maximum of 512px and defaults to a 64px border.
+ The full image is 1024x1024.
+ """
+ option, pixels, flag = self.process_options(option, pixels)
+ if flag is None:
+ return await ctx.send("I don't have that flag!")
+
+ async with ctx.typing():
+ async with aiohttp.ClientSession() as session:
+ try:
+ response = await session.get(url)
+ except aiohttp.client_exceptions.ClientConnectorError:
+ return await ctx.send("Cannot connect to provided URL!")
+ except aiohttp.client_exceptions.InvalidURL:
+ return await ctx.send("Invalid URL!")
+ if response.status != 200:
+ return await ctx.send("Bad response from provided URL!")
+ image_bytes = await response.read()
+ await self.send_image(ctx, image_bytes, pixels, flag, option)
+
+ @prideavatar.command()
+ async def flags(self, ctx: commands.Context) -> None:
+ """This lists the flags that can be used with the prideavatar command."""
+ choices = sorted(set(self.GENDER_OPTIONS.values()))
+ options = "• " + "\n• ".join(choices)
+ embed = discord.Embed(
+ title="I have the following flags:",
+ description=options,
+ colour=Colours.soft_red
+ )
+
+ await ctx.send(embed=embed)
+
+ @pfp_modify.command(
+ name='savatar',
+ aliases=('spookyavatar', 'spookify'),
+ brief='Spookify an user\'s avatar.'
+ )
+ async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None:
+ """A command to print the user's spookified avatar."""
+ if user is None:
+ user = ctx.message.author
+
+ async with ctx.typing():
+ embed = discord.Embed(colour=0xFF0000)
+ embed.title = "Is this you or am I just really paranoid?"
+ embed.set_author(name=str(user.name), icon_url=user.avatar_url)
+
+ image_bytes = await ctx.author.avatar_url.read()
+ im = Image.open(BytesIO(image_bytes))
+ modified_im = spookifications.get_random_effect(im)
+ modified_im.save(str(ctx.message.id)+'.png')
+ f = discord.File(str(ctx.message.id)+'.png')
+ embed.set_image(url='attachment://'+str(ctx.message.id)+'.png')
+
+ await ctx.send(file=f, embed=embed)
+ os.remove(str(ctx.message.id)+'.png')
+
+
+def setup(bot: commands.Bot) -> None:
+ """Load the PfpModify cog."""
+ bot.add_cog(PfpModify(bot))
diff --git a/bot/exts/halloween/spookyavatar.py b/bot/exts/halloween/spookyavatar.py
deleted file mode 100644
index 2d7df678..00000000
--- a/bot/exts/halloween/spookyavatar.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import logging
-import os
-from io import BytesIO
-
-import aiohttp
-import discord
-from PIL import Image
-from discord.ext import commands
-
-from bot.utils.halloween import spookifications
-
-log = logging.getLogger(__name__)
-
-
-class SpookyAvatar(commands.Cog):
- """A cog that spookifies an avatar."""
-
- def __init__(self, bot: commands.Bot):
- self.bot = bot
-
- async def get(self, url: str) -> bytes:
- """Returns the contents of the supplied URL."""
- async with aiohttp.ClientSession() as session:
- async with session.get(url) as resp:
- return await resp.read()
-
- @commands.command(name='savatar', aliases=('spookyavatar', 'spookify'),
- brief='Spookify an user\'s avatar.')
- async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None:
- """A command to print the user's spookified avatar."""
- if user is None:
- user = ctx.message.author
-
- async with ctx.typing():
- embed = discord.Embed(colour=0xFF0000)
- embed.title = "Is this you or am I just really paranoid?"
- embed.set_author(name=str(user.name), icon_url=user.avatar_url)
-
- image_bytes = await ctx.author.avatar_url.read()
- im = Image.open(BytesIO(image_bytes))
- modified_im = spookifications.get_random_effect(im)
- modified_im.save(str(ctx.message.id)+'.png')
- f = discord.File(str(ctx.message.id)+'.png')
- embed.set_image(url='attachment://'+str(ctx.message.id)+'.png')
-
- await ctx.send(file=f, embed=embed)
- os.remove(str(ctx.message.id)+'.png')
-
-
-def setup(bot: commands.Bot) -> None:
- """Spooky avatar Cog load."""
- bot.add_cog(SpookyAvatar(bot))
diff --git a/bot/exts/pride/pride_avatar.py b/bot/exts/pride/pride_avatar.py
deleted file mode 100644
index 2eade796..00000000
--- a/bot/exts/pride/pride_avatar.py
+++ /dev/null
@@ -1,177 +0,0 @@
-import logging
-from io import BytesIO
-from pathlib import Path
-from typing import Tuple
-
-import aiohttp
-import discord
-from PIL import Image, ImageDraw, UnidentifiedImageError
-from discord.ext.commands import Bot, Cog, Context, group
-
-from bot.constants import Colours
-
-log = logging.getLogger(__name__)
-
-OPTIONS = {
- "agender": "agender",
- "androgyne": "androgyne",
- "androgynous": "androgyne",
- "aromantic": "aromantic",
- "aro": "aromantic",
- "ace": "asexual",
- "asexual": "asexual",
- "bigender": "bigender",
- "bisexual": "bisexual",
- "bi": "bisexual",
- "demiboy": "demiboy",
- "demigirl": "demigirl",
- "demi": "demisexual",
- "demisexual": "demisexual",
- "gay": "gay",
- "lgbt": "gay",
- "queer": "gay",
- "homosexual": "gay",
- "fluid": "genderfluid",
- "genderfluid": "genderfluid",
- "genderqueer": "genderqueer",
- "intersex": "intersex",
- "lesbian": "lesbian",
- "non-binary": "nonbinary",
- "enby": "nonbinary",
- "nb": "nonbinary",
- "nonbinary": "nonbinary",
- "omnisexual": "omnisexual",
- "omni": "omnisexual",
- "pansexual": "pansexual",
- "pan": "pansexual",
- "pangender": "pangender",
- "poly": "polysexual",
- "polysexual": "polysexual",
- "polyamory": "polyamory",
- "polyamorous": "polyamory",
- "transgender": "transgender",
- "trans": "transgender",
- "trigender": "trigender"
-}
-
-
-class PrideAvatar(Cog):
- """Put an LGBT spin on your avatar!"""
-
- def __init__(self, bot: Bot):
- self.bot = bot
-
- @staticmethod
- def crop_avatar(avatar: Image) -> Image:
- """This crops the avatar into a circle."""
- mask = Image.new("L", avatar.size, 0)
- draw = ImageDraw.Draw(mask)
- draw.ellipse((0, 0) + avatar.size, fill=255)
- avatar.putalpha(mask)
- return avatar
-
- @staticmethod
- def crop_ring(ring: Image, px: int) -> Image:
- """This crops the ring into a circle."""
- mask = Image.new("L", ring.size, 0)
- draw = ImageDraw.Draw(mask)
- draw.ellipse((0, 0) + ring.size, fill=255)
- draw.ellipse((px, px, 1024-px, 1024-px), fill=0)
- ring.putalpha(mask)
- return ring
-
- @staticmethod
- def process_options(option: str, pixels: int) -> Tuple[str, int, str]:
- """Does some shared preprocessing for the prideavatar commands."""
- return option.lower(), max(0, min(512, pixels)), OPTIONS.get(option)
-
- async def process_image(self, ctx: Context, image_bytes: bytes, pixels: int, flag: str, option: str) -> None:
- """Constructs the final image, embeds it, and sends it."""
- try:
- avatar = Image.open(BytesIO(image_bytes))
- except UnidentifiedImageError:
- return await ctx.send("Cannot identify image from provided URL")
- avatar = avatar.convert("RGBA").resize((1024, 1024))
-
- avatar = self.crop_avatar(avatar)
-
- ring = Image.open(Path(f"bot/resources/pride/flags/{flag}.png")).resize((1024, 1024))
- ring = ring.convert("RGBA")
- ring = self.crop_ring(ring, pixels)
-
- avatar.alpha_composite(ring, (0, 0))
- bufferedio = BytesIO()
- avatar.save(bufferedio, format="PNG")
- bufferedio.seek(0)
-
- file = discord.File(bufferedio, filename="pride_avatar.png") # Creates file to be used in embed
- embed = discord.Embed(
- name="Your Lovely Pride Avatar",
- description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D"
- )
- embed.set_image(url="attachment://pride_avatar.png")
- embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url)
- await ctx.send(file=file, embed=embed)
-
- @group(aliases=["avatarpride", "pridepfp", "prideprofile"], invoke_without_command=True)
- async def prideavatar(self, ctx: Context, option: str = "lgbt", pixels: int = 64) -> None:
- """
- This surrounds an avatar with a border of a specified LGBT flag.
-
- This defaults to the LGBT rainbow flag if none is given.
- The amount of pixels can be given which determines the thickness of the flag border.
- This has a maximum of 512px and defaults to a 64px border.
- The full image is 1024x1024.
- """
- option, pixels, flag = self.process_options(option, pixels)
- if flag is None:
- return await ctx.send("I don't have that flag!")
-
- async with ctx.typing():
- image_bytes = await ctx.author.avatar_url.read()
- await self.process_image(ctx, image_bytes, pixels, flag, option)
-
- @prideavatar.command()
- async def image(self, ctx: Context, url: str, option: str = "lgbt", pixels: int = 64) -> None:
- """
- This surrounds the image specified by the URL with a border of a specified LGBT flag.
-
- This defaults to the LGBT rainbow flag if none is given.
- The amount of pixels can be given which determines the thickness of the flag border.
- This has a maximum of 512px and defaults to a 64px border.
- The full image is 1024x1024.
- """
- option, pixels, flag = self.process_options(option, pixels)
- if flag is None:
- return await ctx.send("I don't have that flag!")
-
- async with ctx.typing():
- async with aiohttp.ClientSession() as session:
- try:
- response = await session.get(url)
- except aiohttp.client_exceptions.ClientConnectorError:
- return await ctx.send("Cannot connect to provided URL!")
- except aiohttp.client_exceptions.InvalidURL:
- return await ctx.send("Invalid URL!")
- if response.status != 200:
- return await ctx.send("Bad response from provided URL!")
- image_bytes = await response.read()
- await self.process_image(ctx, image_bytes, pixels, flag, option)
-
- @prideavatar.command()
- async def flags(self, ctx: Context) -> None:
- """This lists the flags that can be used with the prideavatar command."""
- choices = sorted(set(OPTIONS.values()))
- options = "• " + "\n• ".join(choices)
- embed = discord.Embed(
- title="I have the following flags:",
- description=options,
- colour=Colours.soft_red
- )
-
- await ctx.send(embed=embed)
-
-
-def setup(bot: Bot) -> None:
- """Cog load."""
- bot.add_cog(PrideAvatar(bot))