1
0
Fork 0
mirror of https://github.com/uhIgnacio/EmoteManager.git synced 2024-08-15 02:23:13 +00:00

auto convert static images to GIFs if there's no room (closes #3)

Also backport the image resize code from 70045b2a0e.
This commit is contained in:
Io Mintz 2019-10-10 00:24:12 +00:00
parent 1ad3a7ccec
commit e02022b245
4 changed files with 101 additions and 50 deletions

View file

@ -3,17 +3,20 @@
import asyncio
import cgi
import collections
import contextlib
import io
import logging
import weakref
import operator
import posixpath
import traceback
import contextlib
import urllib.parse
import weakref
import aioec
import aiohttp
import discord
import humanize
from discord.ext import commands
import utils
@ -24,6 +27,9 @@ from utils.paginator import ListPaginator
logger = logging.getLogger(__name__)
class UserCancelledError(commands.UserInputError):
pass
class Emotes(commands.Cog):
IMAGE_MIMETYPES = {'image/png', 'image/jpeg', 'image/gif'}
# TAR_MIMETYPES = {'application/x-tar', 'application/x-xz', 'application/gzip', 'application/x-bzip2'}
@ -107,7 +113,7 @@ class Emotes(commands.Cog):
"""
name, url = self.parse_add_command_args(context, args)
async with context.typing():
message = await self.add_safe(context.guild, name, url, context.message.author.id)
message = await self.add_safe(context, name, url, context.message.author.id)
await context.send(message)
@classmethod
@ -182,7 +188,7 @@ class Emotes(commands.Cog):
f'Original emote author: {utils.format_user(self.bot, emote.author)}')
async with context.typing():
message = await self.add_safe(context.guild, name, emote.url, context.author.id, reason=reason)
message = await self.add_safe(context, name, emote.url, context.author.id, reason=reason)
await context.send(message)
@ -199,12 +205,16 @@ class Emotes(commands.Cog):
raise commands.BadArgument('A URL or attachment must be given.')
url = url or context.message.attachments[0].url
archive = await self.fetch_safe(url, valid_mimetypes=self.ARCHIVE_MIMETYPES)
async with context.typing():
archive = await self.fetch_safe(url, valid_mimetypes=self.ARCHIVE_MIMETYPES)
if type(archive) is str: # error case
await context.send(archive)
return
await self.add_from_archive(context, archive)
with contextlib.suppress(discord.HTTPException):
# so they know when we're done
await context.message.add_reaction(utils.SUCCESS_EMOJIS[True])
async def add_from_archive(self, context, archive):
limit = 50_000_000 # prevent someone from trying to make a giant compressed file
@ -212,7 +222,8 @@ class Emotes(commands.Cog):
if error is None:
name = self.format_emote_filename(posixpath.basename(name))
async with context.typing():
await context.send(await self.add_safe_bytes(context.guild, name, context.author.id, img))
message = await self.add_safe_bytes(context, name, context.author.id, img)
await context.send(message)
continue
if isinstance(error, errors.FileTooBigError):
@ -224,7 +235,7 @@ class Emotes(commands.Cog):
await context.send(f'{name}: {error}')
async def add_safe(self, guild, name, url, author_id, *, reason=None):
async def add_safe(self, context, name, url, author_id, *, reason=None):
"""Try to add an emote. Returns a string that should be sent to the user."""
try:
image_data = await self.fetch_safe(url)
@ -233,7 +244,7 @@ class Emotes(commands.Cog):
if type(image_data) is str: # error case
return image_data
return await self.add_safe_bytes(guild, name, author_id, image_data, reason=reason)
return await self.add_safe_bytes(context, name, author_id, image_data, reason=reason)
async def fetch_safe(self, url, valid_mimetypes=None):
"""Try to fetch a URL. On error return a string that should be sent to the user."""
@ -246,17 +257,34 @@ class Emotes(commands.Cog):
except aiohttp.ClientResponseError as exc:
raise errors.HTTPException(exc.status)
async def add_safe_bytes(self, guild, name, author_id, image_data: bytes, *, reason=None):
"""Try to add an emote from bytes. On error, return a string that should be sent to the user."""
async def add_safe_bytes(self, context, name, author_id, image_data: bytes, *, reason=None):
"""Try to add an emote from bytes. On error, return a string that should be sent to the user.
If the image is static and there are not enough free static slots, prompt to convert the image to a single-frame
gif instead.
"""
counts = collections.Counter(map(operator.attrgetter('animated'), context.guild.emojis))
# >= rather than == because there are sneaky ways to exceed the limit
if counts[False] >= context.guild.emoji_limit and counts[True] >= context.guild.emoji_limit:
# we raise instead of returning a string in order to abort commands that run this function in a loop
raise commands.UserInputError('This server is out of emote slots.')
static = utils.image.mime_type_for_image(image_data) != 'image/gif'
converted = False
if static and counts[False] >= 1: # context.guild.emoji_limit:
image_data = await utils.image.convert_to_gif_in_subprocess(image_data)
converted = True
try:
emote = await self.create_emote_from_bytes(guild, name, author_id, image_data, reason=reason)
emote = await self.create_emote_from_bytes(context.guild, name, author_id, image_data, reason=reason)
except discord.InvalidArgument:
return discord.utils.escape_mentions(f'{name}: The file supplied was not a valid GIF, PNG, or JPEG file.')
except discord.HTTPException as ex:
return discord.utils.escape_mentions(
f'{name}: An error occurred while creating the the emote:\n'
+ utils.format_http_exception(ex))
return f'Emote {emote} successfully created.'
s = f'Emote {emote} successfully created'
return s + ' as a GIF.' if converted else s + '.'
async def fetch(self, url, valid_mimetypes=None):
valid_mimetypes = valid_mimetypes or self.IMAGE_MIMETYPES