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))