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