Files
aikaterna-cogs/blurplefy/blurplefy.py
2022-05-07 11:33:46 -07:00

440 lines
18 KiB
Python

# Ported for Red v3 from: https://github.com/Rocked03/Blurplefied
import aiohttp
import asyncio
import datetime
from io import BytesIO
import logging
import math
from PIL import Image, ImageEnhance, ImageSequence, UnidentifiedImageError
import random
import sys
from resizeimage import resizeimage
from types import SimpleNamespace
import discord
from redbot.core import Config, commands, checks
from redbot.core.utils.predicates import MessagePredicate
log = logging.getLogger("red.aikaterna.blurplefy")
# LEGACY_BLURPLE = (114, 137, 218)
# LEGACY_DARK_BLURPLE = (78, 93, 148)
BLURPLE = (88, 101, 242)
DARK_BLURPLE = (69, 79, 191)
WHITE = (255, 255, 255)
class Blurplefy(commands.Cog):
"""Blurplefy images and check blurple content of images."""
async def red_delete_data_for_user(self, **kwargs):
"""Nothing to delete"""
return
def __init__(self, bot):
"""Blurplefy images and check blurple content of images."""
self.bot = bot
self.config = Config.get_conf(self, 2778931480, force_registration=True)
default_guild = {"role_enabled": False, "blurple_role": None}
self.config.register_guild(**default_guild)
self.session = aiohttp.ClientSession()
@commands.guild_only()
@commands.command()
@checks.admin_or_permissions(manage_roles=True)
async def blurplerole(self, ctx):
"""Toggle a role award for having a blurple profile picture.
A user's profile picture will be checked when they use [p]blurple.
"""
blurple_role_id = await self.config.guild(ctx.guild).blurple_role()
if blurple_role_id is None:
msg = "Enter the role name to award: it needs to be a valid, already existing role, "
msg += "and the name must match exactly (don't use a role mention)."
await ctx.send(msg)
pred = MessagePredicate.same_context(ctx)
try:
blurple_role = await ctx.bot.wait_for("message", timeout=15.0, check=pred)
blurple_role_obj = discord.utils.get(ctx.guild.roles, name=blurple_role.content)
if blurple_role_obj is None:
return await ctx.send("No role with that name.")
return await ctx.invoke(self.blurpleroleset, role_name=blurple_role_obj)
except asyncio.TimeoutError:
return await ctx.send("No role entered, try again later.")
role_enabled = await self.config.guild(ctx.guild).role_enabled()
await self.config.guild(ctx.guild).role_enabled.set(not role_enabled)
await ctx.send(f"Blurple role awarding {'enabled' if not role_enabled else 'disabled'}.")
@commands.guild_only()
@commands.command()
@checks.admin_or_permissions(manage_roles=True)
async def blurpleroleset(self, ctx, *, role_name: discord.Role):
"""Sets the role to award if blurplerole is on."""
await self.config.guild(ctx.guild).blurple_role.set(role_name.id)
blurple_role_id = await self.config.guild(ctx.guild).blurple_role()
blurple_role_obj = discord.utils.get(ctx.guild.roles, id=blurple_role_id)
await ctx.send(f"Blurple award role set to: {blurple_role_obj.name}.")
blurple_role_enabled = await self.config.guild(ctx.guild).role_enabled()
if not blurple_role_enabled:
await ctx.invoke(self.blurplerole)
async def blurplefy(self, ctx, user: discord.Member = None):
"""Blurplefy a user or image."""
picture = None
link = ctx.message.attachments
if user is None and not link:
picture = ctx.author.avatar_url
else:
if not user:
if len(link) != 0:
for image in link:
picture = image.url
else:
picture = user.avatar_url
try:
async with self.session.get(str(picture)) as r:
response = await r.read()
except ValueError:
await ctx.send(f"{ctx.author.display_name}, please link a valid image URL.")
return
@commands.guild_only()
@commands.command()
@commands.cooldown(rate=1, per=30, type=commands.BucketType.user)
async def blurple(self, ctx, user: discord.Member = None):
"""Check a user or an attached uploaded image for blurple content."""
await ctx.trigger_typing()
picture = None
link = ctx.message.attachments
if user is None and not link:
picture = ctx.author.avatar_url
role_check = True
elif not user:
if len(link) != 0:
for image in link:
picture = image.url
role_check = False
else:
picture = user.avatar_url
role_check = False
try:
async with self.session.get(str(picture)) as r:
response = await r.read()
except ValueError:
await ctx.send(f"{ctx.author.display_name}, please link a valid image URL.")
return
try:
im = Image.open(BytesIO(response))
except UnidentifiedImageError:
await ctx.send(f"{ctx.author.display_name}, this doesn't look like an image.")
return
except Exception as exc:
log.exception("Blurplefy encountered an error:\n ", exc_info=exc)
await ctx.send(f"{ctx.author.display_name}, please link a valid image URL.")
return
im = im.convert("RGBA")
imsize = list(im.size)
impixels = imsize[0] * imsize[1]
# 1250x1250 = 1562500
maxpixelcount = 1562500
if impixels > maxpixelcount:
downsizefraction = math.sqrt(maxpixelcount / impixels)
im = resizeimage.resize_width(im, (imsize[0] * downsizefraction))
imsize = list(im.size)
impixels = imsize[0] * imsize[1]
msg = f"{ctx.author.display_name}, image resized smaller for easier processing."
await ctx.send(msg)
image_object = await self.blurple_imager(im, imsize)
image = discord.File(fp=image_object.file, filename=f"{random.randint(1,10000)}_image.png")
blurpleness_percentage = round(((image_object.nooftotalpixels / image_object.noofpixels) * 100), 2)
percent_blurple = round(((image_object.noofblurplepixels / image_object.noofpixels) * 100), 2)
percent_dblurple = round(((image_object.noofdarkblurplepixels / image_object.noofpixels) * 100), 2)
percent_white = round(((image_object.noofwhitepixels / image_object.noofpixels) * 100), 2)
embed = discord.Embed(title="", colour=0x7289DA)
embed.add_field(name=f"Total amount of Blurple", value=f"{blurpleness_percentage}%", inline=False)
embed.add_field(name=f"Blurple (rgb(88, 101, 242))", value=f"{percent_blurple}%", inline=True)
embed.add_field(name=f"White (rgb(255, 255, 255))", value=f"{percent_white}%", inline=True)
embed.add_field(name=f"Dark Blurple (rgb(69, 79, 191))", value=f"{percent_dblurple}%", inline=True)
embed.add_field(
name="Guide",
value="Blurple, White, Dark Blurple = \nBlurple, White, and Dark Blurple (respectively) \nBlack = Not Blurple, White, or Dark Blurple",
inline=False,
)
embed.set_footer(
text=f"Please note: Discord slightly reduces quality of the images, therefore the percentages may be slightly inaccurate.\nContent requested by {str(ctx.author)}"
)
embed.set_image(url="attachment://image.png")
embed.set_thumbnail(url=picture)
await ctx.send(embed=embed, file=image)
blurple_role_enabled = await self.config.guild(ctx.guild).role_enabled()
if role_check and blurple_role_enabled:
blurple_role_id = await self.config.guild(ctx.guild).blurple_role()
blurple_role_obj = discord.utils.get(ctx.guild.roles, id=blurple_role_id)
if not blurple_role_obj:
msg = "The role that is set for the blurple role doesn't exist, so I can't award the role to any qualifying users."
return await ctx.send(msg)
if not ctx.channel.permissions_for(ctx.me).manage_roles:
msg = "I need the Manage Roles permission here to be able to add the set blurple role to users that have a qualifying profile picture set."
return await ctx.send(msg)
if (
blurpleness_percentage > 75
and picture == ctx.author.avatar_url
and blurple_role_obj not in ctx.author.roles
and percent_blurple > 5
):
msg = f"{ctx.author.display_name}, as your profile pic has enough blurple (over 75% in total and over 5% blurple), "
msg += f"you have recieved the role **{blurple_role_obj.name}**!"
await ctx.send(msg)
await ctx.author.add_roles(blurple_role_obj)
elif picture == ctx.author.avatar_url and blurple_role_obj not in ctx.author.roles:
msg = f"{ctx.author.display_name}, your profile pic does not have enough blurple (over 75% in total and over 5% blurple), "
msg += f"therefore you are not eligible for the role {blurple_role_obj.name}."
await ctx.send(msg)
@commands.guild_only()
@commands.command()
@commands.cooldown(rate=1, per=30, type=commands.BucketType.user)
async def blurplefy(self, ctx, user: discord.Member = None):
"""Blurplefy a user or an uploaded image attached to the command."""
await ctx.trigger_typing()
picture = None
link = ctx.message.attachments
if user is None and not link:
picture = ctx.author.avatar_url
else:
if not user:
if len(link) != 0:
for image in link:
picture = image.url
else:
picture = user.avatar_url
try:
async with self.session.get(str(picture)) as r:
response = await r.read()
except ValueError:
await ctx.send(f"{ctx.author.display_name}, please link a valid image URL.")
return
try:
im = Image.open(BytesIO(response))
except UnidentifiedImageError:
await ctx.send(f"{ctx.author.display_name}, this doesn't look like an image.")
return
except Exception as exc:
log.exception("Blurplefy encountered an error:\n ", exc_info=exc)
await ctx.send(f"{ctx.author.display_name}, please link a valid image URL.")
return
imsize = list(im.size)
impixels = imsize[0] * imsize[1]
# 1250x1250 = 1562500
maxpixelcount = 1562500
try:
i = im.info["version"]
isgif = True
gifloop = int(im.info["loop"])
except KeyError:
# no version key
isgif = False
except Exception as exc:
log.exception("Blurplefy encountered an error:\n ", exc_info=exc)
if impixels > maxpixelcount:
downsizefraction = math.sqrt(maxpixelcount / impixels)
im = resizeimage.resize_width(im, (imsize[0] * downsizefraction))
imsize = list(im.size)
impixels = imsize[0] * imsize[1]
await ctx.send(f"{ctx.author.display_name}, image resized smaller for easier processing.")
if isgif is False:
image = await self.imager(im, imsize)
else:
image = await self.gifimager(im, gifloop, imsize)
max_size = 8 * 1024 * 1024
size = sys.getsizeof(image)
if size > max_size:
await ctx.send(
f"{ctx.author.display_name}, whoops! It looks like this image is too big to upload. Try a smaller image (less than 8mb)."
)
return
if isgif is False:
image = discord.File(fp=image, filename="image.png")
else:
image = discord.File(fp=image, filename="image.gif")
embed = discord.Embed(title="", colour=0x7289DA)
embed.set_author(name="Blurplefier - makes your image blurple!")
if isgif is False:
embed.set_image(url="attachment://image.png")
else:
embed.set_image(url="attachment://image.gif")
embed.set_footer(
text=f"Please note - This blurplefier is automated and therefore may not always give you the best result.\nContent requested by {str(ctx.author)}"
)
embed.set_thumbnail(url=picture)
await ctx.send(embed=embed, file=image)
@staticmethod
async def blurple_imager(im, imsize):
colourbuffer = 20
noofblurplepixels = 0
noofwhitepixels = 0
noofdarkblurplepixels = 0
nooftotalpixels = 0
noofpixels = 0
img = im.load()
for x in range(imsize[0]):
i = 1
for y in range(imsize[1]):
pixel = img[x, y]
check = 1
checkblurple = 1
checkwhite = 1
checkdarkblurple = 1
for i in range(3):
if not (BLURPLE[i] + colourbuffer > pixel[i] > BLURPLE[i] - colourbuffer):
checkblurple = 0
if not (DARK_BLURPLE[i] + colourbuffer > pixel[i] > DARK_BLURPLE[i] - colourbuffer):
checkdarkblurple = 0
if not (WHITE[i] + colourbuffer > pixel[i] > WHITE[i] - colourbuffer):
checkwhite = 0
if checkblurple == 0 and checkdarkblurple == 0 and checkwhite == 0:
check = 0
if check == 0:
img[x, y] = (0, 0, 0, 255)
if check == 1:
nooftotalpixels += 1
if checkblurple == 1:
noofblurplepixels += 1
if checkdarkblurple == 1:
noofdarkblurplepixels += 1
if checkwhite == 1:
noofwhitepixels += 1
noofpixels += 1
image_file_object = BytesIO()
im.save(image_file_object, format="png")
image_file_object.seek(0)
return SimpleNamespace(
file=image_file_object,
noofblurplepixels=noofblurplepixels,
noofwhitepixels=noofwhitepixels,
noofdarkblurplepixels=noofdarkblurplepixels,
nooftotalpixels=nooftotalpixels,
noofpixels=noofpixels,
)
@staticmethod
async def imager(im, imsize):
im = im.convert(mode="L")
im = ImageEnhance.Contrast(im).enhance(1000)
im = im.convert(mode="RGB")
img = im.load()
for x in range(imsize[0] - 1):
i = 1
for y in range(imsize[1] - 1):
pixel = img[x, y]
if pixel != (255, 255, 255):
img[x, y] = BLURPLE
image_file_object = BytesIO()
im.save(image_file_object, format="png")
image_file_object.seek(0)
return image_file_object
@staticmethod
async def gifimager(im, gifloop, imsize):
frames = [frame.copy() for frame in ImageSequence.Iterator(im)]
newgif = []
for frame in frames:
frame = frame.convert(mode="L")
frame = ImageEnhance.Contrast(frame).enhance(1000)
frame = frame.convert(mode="RGB")
img = frame.load()
for x in range(imsize[0]):
i = 1
for y in range(imsize[1]):
pixel = img[x, y]
if pixel != (255, 255, 255):
img[x, y] = BLURPLE
newgif.append(frame)
image_file_object = BytesIO()
gif = newgif[0]
gif.save(image_file_object, format="gif", save_all=True, append_images=newgif[1:], loop=0)
image_file_object.seek(0)
return image_file_object
@commands.command()
async def countdown(self, ctx):
"""Countdown to Discord's next anniversary."""
embed = discord.Embed(name="\N{ZERO WIDTH SPACE}", colour=0x7289DA)
now = datetime.datetime.utcnow()
timeleft = datetime.datetime(now.year, 5, 13) + datetime.timedelta(hours=7) - datetime.datetime.utcnow()
discord_years = now.year - 2015
if timeleft.total_seconds() < 0:
timeleft = (
datetime.datetime((now.year + 1), 5, 13) + datetime.timedelta(hours=7) - datetime.datetime.utcnow()
)
discord_years = (now.year + 1) - 2015
discord_years_suffix = self._get_suffix(discord_years)
embed.set_author(name=f"Time left until Discord's {discord_years}{discord_years_suffix} Anniversary")
time = self._dynamic_time(int(timeleft.total_seconds()))
embed.add_field(name="Countdown to midnight, May 13, California time (UTC-7):", value=f"{time}")
await ctx.send(embed=embed)
@staticmethod
def _dynamic_time(time):
m, s = divmod(time, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
if d > 0:
msg = "{0}d {1}h"
elif d == 0 and h > 0:
msg = "{1}h {2}m"
elif d == 0 and h == 0 and m > 0:
msg = "{2}m {3}s"
elif d == 0 and h == 0 and m == 0 and s > 0:
msg = "{3}s"
else:
msg = ""
return msg.format(d, h, m, s)
@staticmethod
def _get_suffix(num):
suffixes = {1: "st", 2: "nd", 3: "rd"}
if 10 <= num % 100 <= 20:
suffix = "th"
else:
suffix = suffixes.get(num % 10, "th")
return suffix
def cog_unload(self):
self.bot.loop.create_task(self.session.close())