From f44b106b60ca90d125ff54e5e39c2b9eb916dce9 Mon Sep 17 00:00:00 2001 From: bmintz Date: Tue, 31 Jul 2018 18:02:44 -0500 Subject: [PATCH 01/23] add invite command --- cogs/meta.py | 28 ++++++++++++++++++++++++++++ config.example.py | 1 + 2 files changed, 29 insertions(+) create mode 100644 cogs/meta.py diff --git a/cogs/meta.py b/cogs/meta.py new file mode 100644 index 0000000..7172c3e --- /dev/null +++ b/cogs/meta.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# encoding: utf-8 + +import discord +from discord.ext import commands + + +class Meta: + def __init__(self, bot): + self.bot = bot + + @commands.command(aliases=['inv']) + async def invite(self, context): + """Gives you a link to add me to your server.""" + permissions = discord.Permissions() + permissions.update(**dict.fromkeys(( + 'read_messages', + 'send_messages', + 'add_reactions', + 'external_emojis', + 'manage_emojis', + 'embed_links', + ), True)) + + await context.send('<%s>' % discord.utils.oauth_url(self.bot.user.id, permissions)) + +def setup(bot): + bot.add_cog(Meta(bot)) diff --git a/config.example.py b/config.example.py index 8f33607..e5ce8c4 100644 --- a/config.example.py +++ b/config.example.py @@ -1,6 +1,7 @@ { 'cogs': ( 'cogs.emote', + 'cogs.meta', 'ben_cogs.debug', 'ben_cogs.misc', 'ben_cogs.debug' From e5da0de672f9be354d28608205549874af3600be Mon Sep 17 00:00:00 2001 From: bmintz Date: Tue, 31 Jul 2018 20:46:21 -0500 Subject: [PATCH 02/23] add add-from-ec command --- cogs/emote.py | 42 ++++++++++++++++++++++++++++++++++++------ config.example.py | 2 ++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/cogs/emote.py b/cogs/emote.py index 6d3514d..639b094 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -8,6 +8,7 @@ import logging import weakref import traceback import contextlib +import urllib.parse import aiohttp import discord @@ -126,10 +127,37 @@ class Emotes: return name, url - async def add_safe(self, guild, name, url, author_id): + @commands.command(name='add-from-ec', aliases=['addfromec']) + async def add_from_ec(self, context, name): + """Copies an emote from Emoji Connoisseur to your server. + + The list of possible emotes you can copy is here: + https://emoji-connoissuer.python-for.life/list + """ + async with self.http.get( + self.bot.config['ec_api_url'] + '/emote/' + urllib.parse.quote(name, safe='') + ) as resp: + print(resp.url) + if resp.status == 404: + return await context.send("Emote not found in Emoji Connoisseur's database.") + + emote = await resp.json() + + reason = ( + f'Added from Emoji Connoisseur by {utils.format_user(self.bot, context.author.id)}. ' + f'Original emote author: {utils.format_user(self.bot, int(emote["author"]))}') + + async with context.typing(): + message = await self.add_safe(context.guild, name, utils.emote.url( + emote['id'], animated=emote['animated'] + ), context.author.id, reason=reason) + + await context.send(message) + + async def add_safe(self, guild, name, url, author_id, *, reason=None): """Try to add an emote. Returns a string that should be sent to the user.""" try: - emote = await self.add_from_url(guild, name, url, author_id) + emote = await self.add_from_url(guild, name, url, author_id, reason=reason) except discord.HTTPException as ex: return ( 'An error occurred while creating the emote:\n' @@ -141,9 +169,9 @@ class Emotes: else: return f'Emote {emote} successfully created.' - async def add_from_url(self, guild, name, url, author_id): + async def add_from_url(self, guild, name, url, author_id, *, reason=None): image_data = await self.fetch_emote(url) - emote = await self.create_emote_from_bytes(guild, name, author_id, image_data) + emote = await self.create_emote_from_bytes(guild, name, author_id, image_data, reason=reason) return emote @@ -161,15 +189,17 @@ class Emotes: raise errors.HTTPException(response.status) return io.BytesIO(await response.read()) - async def create_emote_from_bytes(self, guild, name, author_id, image_data: io.BytesIO): + async def create_emote_from_bytes(self, guild, name, author_id, image_data: io.BytesIO, *, reason=None): # resize_until_small is normally blocking, because wand is. # run_in_executor is magic that makes it non blocking somehow. # also, None as the executor arg means "use the loop's default executor" image_data = await self.bot.loop.run_in_executor(None, utils.image.resize_until_small, image_data) + if reason is None: + reason = f'Created by {utils.format_user(self.bot, author_id)}' return await guild.create_custom_emoji( name=name, image=image_data.read(), - reason=f'Created by {utils.format_user(self.bot, author_id)}') + reason=reason) @commands.command() async def remove(self, context, *names): diff --git a/config.example.py b/config.example.py index e5ce8c4..6253987 100644 --- a/config.example.py +++ b/config.example.py @@ -18,4 +18,6 @@ }, 'user_agent': 'EmojiManagerBot (https://github.com/bmintz/emoji-manager-bot)', + + 'ec_api_url': 'https://emoji-connoisseur.python-for.life/api/v0', } From 95848c31630e1b8d82a1f1f4a7e6b0d42ab88033 Mon Sep 17 00:00:00 2001 From: bmintz Date: Tue, 31 Jul 2018 21:05:23 -0500 Subject: [PATCH 03/23] add description --- bot.py | 9 ++++++--- config.example.py | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bot.py b/bot.py index 50ff06c..6f7b2e4 100755 --- a/bot.py +++ b/bot.py @@ -13,13 +13,16 @@ logger.setLevel(logging.INFO) class Bot(commands.AutoShardedBot): def __init__(self, **kwargs): - super().__init__(command_prefix=commands.when_mentioned, **kwargs) - with open('config.py') as f: self.config = eval(f.read(), {}) + super().__init__( + command_prefix=commands.when_mentioned, + description=self.config.get('description', ''), + **kwargs) + for cog in self.config['cogs']: - self.load_extension(cog) + self.load_extension(cog) def run(self): super().run(self.config['tokens'].pop('discord')) diff --git a/config.example.py b/config.example.py index 6253987..cf166e0 100644 --- a/config.example.py +++ b/config.example.py @@ -1,4 +1,9 @@ { + 'description': + 'Emote Manager lets you manage custom server emotes from your phone. ' + 'NOTE: Most commands will be unavailable until both you and the bot have the ' + '"Manage Emojis" permission.', + 'cogs': ( 'cogs.emote', 'cogs.meta', From 85eb1ce741e388c2e851850361a7bf3f3e998e01 Mon Sep 17 00:00:00 2001 From: bmintz Date: Tue, 31 Jul 2018 21:07:19 -0500 Subject: [PATCH 04/23] make the default description a bit easier to read --- config.example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.example.py b/config.example.py index cf166e0..8582c56 100644 --- a/config.example.py +++ b/config.example.py @@ -1,6 +1,6 @@ { 'description': - 'Emote Manager lets you manage custom server emotes from your phone. ' + 'Emote Manager lets you manage custom server emotes from your phone.\n\n' 'NOTE: Most commands will be unavailable until both you and the bot have the ' '"Manage Emojis" permission.', From 402da00cd9b99ca4d3632ffccee454ca4d791274 Mon Sep 17 00:00:00 2001 From: bmintz Date: Fri, 10 Aug 2018 08:01:15 -0500 Subject: [PATCH 05/23] add support command --- cogs/meta.py | 17 +++++++++++++++++ config.example.py | 2 ++ 2 files changed, 19 insertions(+) diff --git a/cogs/meta.py b/cogs/meta.py index 7172c3e..06f1710 100644 --- a/cogs/meta.py +++ b/cogs/meta.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # encoding: utf-8 +import contextlib + import discord from discord.ext import commands @@ -24,5 +26,20 @@ class Meta: await context.send('<%s>' % discord.utils.oauth_url(self.bot.user.id, permissions)) + @commands.command() + async def support(self, context): + """Directs you to the support server.""" + try: + await context.author.send(self.bot.config['support_server_invite']) + with contextlib.suppress(discord.HTTPException): + await context.message.add_reaction('📬') + except discord.Forbidden: + with contextlib.suppress(discord.HTTPException): + await context.message.add_reaction('❌') + await context.send('Unable to send invite in DMs. Please allow DMs from server members.') + def setup(bot): bot.add_cog(Meta(bot)) + + if not bot.config.get('support_server_invite'): + bot.remove_command('support') diff --git a/config.example.py b/config.example.py index 8582c56..22517e4 100644 --- a/config.example.py +++ b/config.example.py @@ -4,6 +4,8 @@ 'NOTE: Most commands will be unavailable until both you and the bot have the ' '"Manage Emojis" permission.', + 'support_server_invite': 'https://discord.gg/some-invite', + 'cogs': ( 'cogs.emote', 'cogs.meta', From 95048a590ccf6d0fd5d48fd3c462b89b2f27badb Mon Sep 17 00:00:00 2001 From: bmintz Date: Sat, 11 Aug 2018 12:26:07 -0500 Subject: [PATCH 06/23] paginator: make sure reactions are to the sole message --- utils/paginator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/paginator.py b/utils/paginator.py index b96a544..ad331ee 100644 --- a/utils/paginator.py +++ b/utils/paginator.py @@ -14,8 +14,8 @@ class Paginator: def __init__(self, ctx: Context, pages: typing.Iterable, *, timeout=300, delete_message=False, predicate=None, delete_message_on_timeout=False, text_message=None): if predicate is None: - def predicate(_, user): - return user == ctx.message.author + def predicate(reaction, user): + return user == ctx.message.author and reaction.message == self._message self.pages = list(pages) self.predicate = predicate From a830769987361fe097c132ed096136e090282df6 Mon Sep 17 00:00:00 2001 From: bmintz Date: Sat, 11 Aug 2018 13:43:50 -0500 Subject: [PATCH 07/23] paginator: actually fix reactions --- utils/paginator.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/utils/paginator.py b/utils/paginator.py index ad331ee..41b52fd 100644 --- a/utils/paginator.py +++ b/utils/paginator.py @@ -11,15 +11,12 @@ from discord.ext.commands import Context class Paginator: - def __init__(self, ctx: Context, pages: typing.Iterable, *, timeout=300, delete_message=False, predicate=None, + def __init__(self, ctx: Context, pages: typing.Iterable, *, timeout=300, delete_message=False, delete_message_on_timeout=False, text_message=None): - if predicate is None: - def predicate(reaction, user): - return user == ctx.message.author and reaction.message == self._message self.pages = list(pages) - self.predicate = predicate self.timeout = timeout + self.author = ctx.author self.target = ctx.channel self.delete_msg = delete_message self.delete_msg_timeout = delete_message_on_timeout @@ -41,6 +38,15 @@ class Paginator: self._page = None + def react_check(self, reaction, user): + if user is None or user != self.author: + return False + + if reaction.message.id != self._message.id: + return False + + return bool(discord.utils.find(lambda emoji: reaction.emoji == emoji, self.navigation)) + async def begin(self): """Starts pagination""" self._stopped = False @@ -50,7 +56,10 @@ class Paginator: await self._message.add_reaction(button) while not self._stopped: try: - reaction, user = await self._client.wait_for('reaction_add', check=self.predicate, timeout=self.timeout) + reaction, user = await self._client.wait_for( + 'reaction_add', + check=self.react_check, + timeout=self.timeout) except asyncio.TimeoutError: await self.stop(delete=self.delete_msg_timeout) continue From e366cb45375497d8c2283c0d3a3147ee2c20bc26 Mon Sep 17 00:00:00 2001 From: bmintz Date: Sat, 11 Aug 2018 21:06:39 -0500 Subject: [PATCH 08/23] allow anyone to use the list command --- cogs/emote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/emote.py b/cogs/emote.py index 639b094..a41983b 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -52,7 +52,7 @@ class Emotes: ): raise errors.MissingManageEmojisPermission - return True + return context.command is self.list async def on_command_error(self, context, error): if isinstance(error, (errors.EmoteManagerError, errors.MissingManageEmojisPermission)): From 7b843d0bd9df239a64241a66c256daa9932f3404 Mon Sep 17 00:00:00 2001 From: bmintz Date: Sat, 11 Aug 2018 21:08:23 -0500 Subject: [PATCH 09/23] fix allowing @Emote Manager list --- cogs/emote.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cogs/emote.py b/cogs/emote.py index a41983b..265605a 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -44,7 +44,9 @@ class Emotes: async def __local_check(self, context): if not context.guild: raise commands.NoPrivateMessage - return False + + if context.command is self.list: + return True if ( not context.author.guild_permissions.manage_emojis @@ -52,7 +54,7 @@ class Emotes: ): raise errors.MissingManageEmojisPermission - return context.command is self.list + return True async def on_command_error(self, context, error): if isinstance(error, (errors.EmoteManagerError, errors.MissingManageEmojisPermission)): From 3550d5c5027e56327001b2e4ee9e4b0b73591bfe Mon Sep 17 00:00:00 2001 From: bmintz Date: Tue, 14 Aug 2018 00:08:34 -0500 Subject: [PATCH 10/23] cogs/emote: remove one print, add another --- cogs/emote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/emote.py b/cogs/emote.py index 265605a..2dd5ae4 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -52,6 +52,7 @@ class Emotes: not context.author.guild_permissions.manage_emojis or not context.guild.me.guild_permissions.manage_emojis ): + logger.error('emote: check failed') raise errors.MissingManageEmojisPermission return True @@ -139,7 +140,6 @@ class Emotes: async with self.http.get( self.bot.config['ec_api_url'] + '/emote/' + urllib.parse.quote(name, safe='') ) as resp: - print(resp.url) if resp.status == 404: return await context.send("Emote not found in Emoji Connoisseur's database.") From 9cd8ed4189518a71db1092d0cc1a8889ee355f76 Mon Sep 17 00:00:00 2001 From: bmintz Date: Tue, 14 Aug 2018 00:11:38 -0500 Subject: [PATCH 11/23] remove one debug log statement --- cogs/emote.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cogs/emote.py b/cogs/emote.py index 2dd5ae4..c81a608 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -52,7 +52,6 @@ class Emotes: not context.author.guild_permissions.manage_emojis or not context.guild.me.guild_permissions.manage_emojis ): - logger.error('emote: check failed') raise errors.MissingManageEmojisPermission return True From 08aef03d6b773147c9bb7d07f40fc95db0606739 Mon Sep 17 00:00:00 2001 From: bmintz Date: Tue, 14 Aug 2018 00:16:43 -0500 Subject: [PATCH 12/23] add-from-ec: handle cases where the API is down --- cogs/emote.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cogs/emote.py b/cogs/emote.py index c81a608..4d87541 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -141,6 +141,9 @@ class Emotes: ) as resp: if resp.status == 404: return await context.send("Emote not found in Emoji Connoisseur's database.") + if resp.status != 200: + return await context.send( + f'Error: the Emoji Connoisseur API returned status code {resp.status}') emote = await resp.json() From 15f186930e859cf391ece46b9b48498813aa0945 Mon Sep 17 00:00:00 2001 From: Ben Mintz Date: Wed, 15 Aug 2018 15:34:57 -0500 Subject: [PATCH 13/23] Create README.md --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..09b821c --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Emote Manager + +[![Discord Bots](https://discordbots.org/api/widget/status/473370418007244852.svg?noavatar=true)](https://discordbots.org/bot/473370418007244852) + +Need to edit your server's custom emotes from your phone? Just add this simple bot, and use its commands to do it for you! + +**Note:** both you and the bot will need the "Manage Emojis" permission to edit custom server emotes. + +To add the bot to your server, visit https://discordapp.com/oauth2/authorize?client_id=473370418007244852&scope=bot&permissions=1074023488. + +## Commands + +

+ To add an emote: +

    +
  • @Emote Manager add :thonkang: (if you already have that emote) +
  • @Emote Manager add rollsafe <https://image.noelshack.com/fichiers/2017/06/1486495269-rollsafe.png> +
  • @Emote Manager add speedtest https://cdn.discordapp.com/emojis/379127000398430219.png +
+ If you invoke @Emote Manager add with an image upload, the image will be used as the emote image, and the filename will be used as the emote name. To choose a different name, simply run it like
+ @Emote Manager add :some_emote: instead. +

+ +

+ @Emote Manager list gives you a list of all emotes on this server. +

+ +

+ @Emote Manager remove emote will remove :emote:. +

+ +

+ @Emote Manager rename old_name new_name will rename :old_name: to :new_name:. +

From be2343988b3a409caf5dc7d99dfb5208d79cc08a Mon Sep 17 00:00:00 2001 From: bmintz Date: Fri, 17 Aug 2018 05:34:31 +0000 Subject: [PATCH 14/23] use aioec --- cogs/emote.py | 23 ++++++++++++----------- requirements.txt | 1 + 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cogs/emote.py b/cogs/emote.py index 4d87541..48d6cc0 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -10,6 +10,7 @@ import traceback import contextlib import urllib.parse +import aioec import aiohttp import discord from discord.ext import commands @@ -29,11 +30,13 @@ class Emotes: self.bot.config['user_agent'] + ' ' + self.bot.http.user_agent }) + self.aioec = aioec.Client(loop=self.bot.loop) # keep track of paginators so we can end them when the cog is unloaded self.paginators = weakref.WeakSet() def __unload(self): self.bot.loop.create_task(self.http.close()) + self.bot.loop.create_task(self.aioec.close()) async def stop_all_paginators(): for paginator in self.paginators: @@ -136,24 +139,22 @@ class Emotes: The list of possible emotes you can copy is here: https://emoji-connoissuer.python-for.life/list """ - async with self.http.get( - self.bot.config['ec_api_url'] + '/emote/' + urllib.parse.quote(name, safe='') - ) as resp: - if resp.status == 404: - return await context.send("Emote not found in Emoji Connoisseur's database.") - if resp.status != 200: - return await context.send( - f'Error: the Emoji Connoisseur API returned status code {resp.status}') - emote = await resp.json() + try: + emote = await self.aioec.emote(name) + except aioec.NotFound: + return await context.send("Emote not found in Emoji Connoisseur's database.") + except aioec.HttpException as exception: + return await context.send( + f'Error: the Emoji Connoisseur API returned status code {exception.status}') reason = ( f'Added from Emoji Connoisseur by {utils.format_user(self.bot, context.author.id)}. ' - f'Original emote author: {utils.format_user(self.bot, int(emote["author"]))}') + f'Original emote author: {utils.format_user(self.bot, emote.author)}') async with context.typing(): message = await self.add_safe(context.guild, name, utils.emote.url( - emote['id'], animated=emote['animated'] + emote.id, animated=emote.animated ), context.author.id, reason=reason) await context.send(message) diff --git a/requirements.txt b/requirements.txt index 6b4c9fd..47890d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +aioec git+https://github.com/Rapptz/discord.py@rewrite jishaku ben_cogs From 67027e5f0feab3db3a9a93f9fe7a5739d2422909 Mon Sep 17 00:00:00 2001 From: bmintz Date: Fri, 17 Aug 2018 07:13:38 +0000 Subject: [PATCH 15/23] cogs/emote: add some aliases --- cogs/emote.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cogs/emote.py b/cogs/emote.py index 48d6cc0..2b7dd49 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -206,7 +206,7 @@ class Emotes: image=image_data.read(), reason=reason) - @commands.command() + @commands.command(aliases=('delete', 'delet', 'rm')) async def remove(self, context, *names): """Remove an emote from this server. @@ -220,7 +220,7 @@ class Emotes: for name in names: await context.invoke(self.remove, name) - @commands.command() + @commands.command(aliases=('mv',)) async def rename(self, context, old_name, new_name): """Rename an emote on this server. @@ -239,7 +239,7 @@ class Emotes: await context.send(f'Emote \:{old_name}: successfully renamed to \:{new_name}:') - @commands.command() + @commands.command(aliases=('ls',)) async def list(self, context): """A list of all emotes on this server. From 8f170d7f1332b5d39ac232c6e3e25dde27caf1ff Mon Sep 17 00:00:00 2001 From: bmintz Date: Fri, 17 Aug 2018 07:35:16 +0000 Subject: [PATCH 16/23] allow use of the actual emote in place of the emote name --- cogs/emote.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/cogs/emote.py b/cogs/emote.py index 2b7dd49..d83379a 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -139,7 +139,7 @@ class Emotes: The list of possible emotes you can copy is here: https://emoji-connoissuer.python-for.life/list """ - + name = name.strip(':') try: emote = await self.aioec.emote(name) except aioec.NotFound: @@ -207,27 +207,29 @@ class Emotes: reason=reason) @commands.command(aliases=('delete', 'delet', 'rm')) - async def remove(self, context, *names): + async def remove(self, context, *emotes): """Remove an emote from this server. - names: the names of one or more emotes you'd like to remove. + emotes: the name of an emote or of one or more emotes you'd like to remove. """ - if len(names) == 1: - emote = await self.disambiguate(context, names[0]) + if len(emotes) == 1: + emote = await self.parse_emote(context, emotes[0]) await emote.delete(reason=f'Removed by {utils.format_user(self.bot, context.author.id)}') await context.send(f'Emote \:{emote.name}: successfully removed.') else: - for name in names: - await context.invoke(self.remove, name) + for emote in emotes: + await context.invoke(self.remove, emote) + with contextlib.suppress(discord.HTTPException): + await context.message.add_reaction('✅') @commands.command(aliases=('mv',)) - async def rename(self, context, old_name, new_name): + async def rename(self, context, old, new_name): """Rename an emote on this server. - old_name: the name of the emote to rename + old: the name of the emote to rename, or the emote itself new_name: what you'd like to rename it to """ - emote = await self.disambiguate(context, old_name) + emote = await self.parse_emote(context, old) try: await emote.edit( name=new_name, @@ -237,9 +239,9 @@ class Emotes: 'An error occurred while renaming the emote:\n' + utils.format_http_exception(ex)) - await context.send(f'Emote \:{old_name}: successfully renamed to \:{new_name}:') + await context.send(f'Emote successfully renamed to \:{new_name}:') - @commands.command(aliases=('ls',)) + @commands.command(aliases=('ls', 'dir')) async def list(self, context): """A list of all emotes on this server. @@ -258,7 +260,18 @@ class Emotes: self.paginators.add(paginator) await paginator.begin() + async def parse_emote(self, context, name_or_emote): + match = utils.emote.RE_CUSTOM_EMOTE.match(name_or_emote) + if match: + id = int(match.group('id')) + emote = discord.utils.get(context.guild.emojis, id=id) + if emote: + return emote + name = name_or_emote + return await self.disambiguate(context, name) + async def disambiguate(self, context, name): + name = name.strip(':') # in case the user tries :foo: and foo is animated candidates = [e for e in context.guild.emojis if e.name.lower() == name.lower() and e.require_colons] if not candidates: raise errors.EmoteNotFoundError(name) From ecefefb4c9f6f3985610179dbcc9c6b136b9a434 Mon Sep 17 00:00:00 2001 From: bmintz Date: Wed, 22 Aug 2018 15:27:35 +0000 Subject: [PATCH 17/23] update the success emotes --- bot.py | 11 +++++++++++ cogs/emote.py | 4 ++-- cogs/meta.py | 4 ++-- config.example.py | 13 +++++++++++++ utils/errors.py | 2 +- utils/misc.py | 5 ----- 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/bot.py b/bot.py index 6f7b2e4..b3f3089 100755 --- a/bot.py +++ b/bot.py @@ -21,9 +21,20 @@ class Bot(commands.AutoShardedBot): description=self.config.get('description', ''), **kwargs) + self._setup_success_emojis() + for cog in self.config['cogs']: self.load_extension(cog) + def _setup_success_emojis(self): + """Load the emojis from the config to be used when a command fails or succeeds + We do it this way so that they can be used anywhere instead of requiring a bot instance. + """ + import utils.misc + default = ('❌', '✅') + utils.SUCCESS_EMOJIS = utils.misc.SUCCESS_EMOJIS = ( + self.config.get('response_emojis', {}).get('success', default)) + def run(self): super().run(self.config['tokens'].pop('discord')) diff --git a/cogs/emote.py b/cogs/emote.py index d83379a..054870f 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -65,7 +65,7 @@ class Emotes: if isinstance(error, commands.NoPrivateMessage): await context.send( - f'{utils.SUCCESS_EMOTES[False]} Sorry, this command may only be used in a server.') + f'{utils.SUCCESS_EMOJIS[False]} Sorry, this command may only be used in a server.') @commands.command() async def add(self, context, *args): @@ -220,7 +220,7 @@ class Emotes: for emote in emotes: await context.invoke(self.remove, emote) with contextlib.suppress(discord.HTTPException): - await context.message.add_reaction('✅') + await context.message.add_reaction(utils.SUCCESS_EMOJIS[True]) @commands.command(aliases=('mv',)) async def rename(self, context, old, new_name): diff --git a/cogs/meta.py b/cogs/meta.py index 06f1710..19cb178 100644 --- a/cogs/meta.py +++ b/cogs/meta.py @@ -32,10 +32,10 @@ class Meta: try: await context.author.send(self.bot.config['support_server_invite']) with contextlib.suppress(discord.HTTPException): - await context.message.add_reaction('📬') + await context.message.add_reaction('📬') # TODO make this emoji configurable too except discord.Forbidden: with contextlib.suppress(discord.HTTPException): - await context.message.add_reaction('❌') + await context.message.add_reaction(utils.SUCCESS_EMOJIS[True]) await context.send('Unable to send invite in DMs. Please allow DMs from server members.') def setup(bot): diff --git a/config.example.py b/config.example.py index 22517e4..b22a2d8 100644 --- a/config.example.py +++ b/config.example.py @@ -27,4 +27,17 @@ 'user_agent': 'EmojiManagerBot (https://github.com/bmintz/emoji-manager-bot)', 'ec_api_url': 'https://emoji-connoisseur.python-for.life/api/v0', + + # emotes that the bot may use to respond to you + # If not provided, the bot will use ('❌', '✅') instead. + # + # You can obtain these ones from the discordbots.org server under the name "tickNo" and "tickYes" + # but I uploaded them to my test server + # so that both the staging and the stable versions of the bot can use them + 'response_emotes': { + 'success': { # emotes used to indicate success or failure + False: '<:error:478164511879069707>', + True: '<:success:478164452261363712>' + }, + }, } diff --git a/utils/errors.py b/utils/errors.py index 35d5667..b3a323f 100644 --- a/utils/errors.py +++ b/utils/errors.py @@ -11,7 +11,7 @@ class MissingManageEmojisPermission(commands.MissingPermissions): def __init__(self): super(Exception, self).__init__( - f'{utils.SUCCESS_EMOTES[False]} ' + f'{utils.SUCCESS_EMOJIS[False]} ' "Sorry, you don't have enough permissions to run this command. " 'You and I both need the Manage Emojis permission.') diff --git a/utils/misc.py b/utils/misc.py index b2a27a0..e4b5e69 100644 --- a/utils/misc.py +++ b/utils/misc.py @@ -5,11 +5,6 @@ import discord """various utilities for use within the bot""" -"""Emotes used to indicate success/failure. You can obtain these from the discordbots.org guild, -but I uploaded them to my test server -so that both the staging and the stable versions of the bot can use them""" -SUCCESS_EMOTES = ('<:error:416845770239508512>', '<:success:416845760810844160>') - def format_user(bot, id, *, mention=False): """Format a user ID for human readable display.""" user = bot.get_user(id) From ac8a9aa3205801242f03315c171a835e5ac38d51 Mon Sep 17 00:00:00 2001 From: bmintz Date: Fri, 7 Sep 2018 21:50:17 +0000 Subject: [PATCH 18/23] cogs/emote: fix local check --- cogs/emote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/emote.py b/cogs/emote.py index 054870f..40541ed 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -45,7 +45,7 @@ class Emotes: self.bot.loop.create_task(stop_all_paginators()) async def __local_check(self, context): - if not context.guild: + if not context.guild or not isinstance(context.author, discord.Member): raise commands.NoPrivateMessage if context.command is self.list: From 0523b2116fcdcea4595f43ebfa8c7e2c6611a77a Mon Sep 17 00:00:00 2001 From: bmintz Date: Sun, 9 Sep 2018 04:16:42 +0000 Subject: [PATCH 19/23] rename to Emote Collector --- cogs/emote.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cogs/emote.py b/cogs/emote.py index 40541ed..f5a05f4 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -134,22 +134,22 @@ class Emotes: @commands.command(name='add-from-ec', aliases=['addfromec']) async def add_from_ec(self, context, name): - """Copies an emote from Emoji Connoisseur to your server. + """Copies an emote from Emote Collector to your server. The list of possible emotes you can copy is here: - https://emoji-connoissuer.python-for.life/list + https://emote-collector.python-for.life/list """ name = name.strip(':') try: emote = await self.aioec.emote(name) except aioec.NotFound: - return await context.send("Emote not found in Emoji Connoisseur's database.") + return await context.send("Emote not found in Emote Collector's database.") except aioec.HttpException as exception: return await context.send( - f'Error: the Emoji Connoisseur API returned status code {exception.status}') + f'Error: the Emote Collector API returned status code {exception.status}') reason = ( - f'Added from Emoji Connoisseur by {utils.format_user(self.bot, context.author.id)}. ' + f'Added from Emote Collector by {utils.format_user(self.bot, context.author.id)}. ' f'Original emote author: {utils.format_user(self.bot, emote.author)}') async with context.typing(): From 0b731ed0fcb8aeb03050b825735a22aee9f4f336 Mon Sep 17 00:00:00 2001 From: bmintz Date: Sun, 9 Sep 2018 04:56:16 +0000 Subject: [PATCH 20/23] update config.example.py --- config.example.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/config.example.py b/config.example.py index b22a2d8..a1c2907 100644 --- a/config.example.py +++ b/config.example.py @@ -24,9 +24,7 @@ }, }, - 'user_agent': 'EmojiManagerBot (https://github.com/bmintz/emoji-manager-bot)', - - 'ec_api_url': 'https://emoji-connoisseur.python-for.life/api/v0', + 'user_agent': 'EmoteManagerBot (https://github.com/bmintz/emote-manager-bot)', # emotes that the bot may use to respond to you # If not provided, the bot will use ('❌', '✅') instead. From c53f60f0d3076b0a2d08e5c7fad0a972764cb602 Mon Sep 17 00:00:00 2001 From: bmintz Date: Tue, 9 Oct 2018 06:07:37 +0000 Subject: [PATCH 21/23] add/remove: require at least one emote, and allow more --- cogs/emote.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/cogs/emote.py b/cogs/emote.py index f5a05f4..7e8ed06 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -81,11 +81,7 @@ class Emotes: `add` will upload a new emote using the first attachment as the image, and its filename as the name """ - try: - name, url = self.parse_add_command_args(context, args) - except commands.BadArgument as exception: - return await context.send(exception) - + 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) await context.send(message) @@ -133,12 +129,17 @@ class Emotes: return name, url @commands.command(name='add-from-ec', aliases=['addfromec']) - async def add_from_ec(self, context, name): - """Copies an emote from Emote Collector to your server. + async def add_from_ec(self, context, name, *names): + """Copies one or more emotes from Emote Collector to your server. The list of possible emotes you can copy is here: https://emote-collector.python-for.life/list """ + if names: + for name in (name,) + names: + await context.invoke(self.add_from_ec, name) + return + name = name.strip(':') try: emote = await self.aioec.emote(name) @@ -207,17 +208,17 @@ class Emotes: reason=reason) @commands.command(aliases=('delete', 'delet', 'rm')) - async def remove(self, context, *emotes): + async def remove(self, context, emote, *emotes): """Remove an emote from this server. emotes: the name of an emote or of one or more emotes you'd like to remove. """ - if len(emotes) == 1: - emote = await self.parse_emote(context, emotes[0]) + if not emotes: + emote = await self.parse_emote(context, emote) await emote.delete(reason=f'Removed by {utils.format_user(self.bot, context.author.id)}') await context.send(f'Emote \:{emote.name}: successfully removed.') else: - for emote in emotes: + for emote in (emote,) + emotes: await context.invoke(self.remove, emote) with contextlib.suppress(discord.HTTPException): await context.message.add_reaction(utils.SUCCESS_EMOJIS[True]) From fa6b43d246c7c3c118602498048008ebb7ee15a8 Mon Sep 17 00:00:00 2001 From: bmintz Date: Tue, 9 Oct 2018 06:07:52 +0000 Subject: [PATCH 22/23] add: improve command usage string --- cogs/emote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/emote.py b/cogs/emote.py index 7e8ed06..b085d64 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -67,7 +67,7 @@ class Emotes: await context.send( f'{utils.SUCCESS_EMOJIS[False]} Sorry, this command may only be used in a server.') - @commands.command() + @commands.command(usage='[name] ') async def add(self, context, *args): """Add a new emote to this server. From 6028b60067c0299cf40ad32bfa5a7da0781cac98 Mon Sep 17 00:00:00 2001 From: bmintz Date: Tue, 9 Oct 2018 06:08:20 +0000 Subject: [PATCH 23/23] fix "add " not being animated --- cogs/emote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/emote.py b/cogs/emote.py index b085d64..6470dc3 100644 --- a/cogs/emote.py +++ b/cogs/emote.py @@ -112,7 +112,7 @@ class Emotes: if match is None: url = utils.strip_angle_brackets(args[1]) else: - url = utils.emote.url(match.group('id')) + url = utils.emote.url(match['id'], animated=match['animated']) return name, url