From 9f487cb57c5c13ec88206b0e92c1512ed0df6dad Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sat, 31 Mar 2018 16:36:30 -0400
Subject: [PATCH] File map update

---
 Pipfile                |    3 +-
 Pipfile.lock           |   52 +-
 src/cogs/booru.py      | 3322 ++++++++++++++++++++--------------------
 src/cogs/management.py |  470 +++---
 src/cogs/owner.py      |  490 +++---
 src/utils/utils.py     |  382 ++---
 6 files changed, 2361 insertions(+), 2358 deletions(-)

diff --git a/Pipfile b/Pipfile
index 436d9ae..ae1d739 100644
--- a/Pipfile
+++ b/Pipfile
@@ -4,6 +4,8 @@ url = "https://pypi.python.org/simple"
 verify_ssl = true
 name = "pypi"
 
+[requires]
+python_version = "3.6"
 
 [packages]
 
@@ -25,4 +27,3 @@ pynacl = "*"
 
 
 [dev-packages]
-
diff --git a/Pipfile.lock b/Pipfile.lock
index d1780c4..b492a8a 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "19fd84259fea739bf9a47e9ea60f364a06fd636ae4eaf50d40683569f9830f7e"
+            "sha256": "c8426f63f07b00a7dcc5220e1210acfc6279bb1eccb40d9a884d34e203bf8f85"
         },
         "host-environment-markers": {
             "implementation_name": "cpython",
@@ -17,7 +17,9 @@
             "sys_platform": "darwin"
         },
         "pipfile-spec": 6,
-        "requires": {},
+        "requires": {
+            "python_version": "3.6"
+        },
         "sources": [
             {
                 "name": "pypi",
@@ -29,30 +31,23 @@
     "default": {
         "aiohttp": {
             "hashes": [
-                "sha256:834f687b806fbf49cb135b5a208b5c27338e19c219d6e09e9049936e01e8bea8",
-                "sha256:6b8c5a00432b8a5a083792006e8fdfb558b8b10019ce254200855264d3a25895",
-                "sha256:7b407c22b0ab473ffe0a7d3231f2084a8ae80fdb64a31842b88d57d6b7bdab7c",
-                "sha256:14821eb8613bfab9118be3c55afc87bf4cef97689896fa0874c6877b117afbeb",
-                "sha256:8f32a4e157bad9c60ebc38c3bb93fcc907a020b017ddf8f7ab1580390e21940e",
-                "sha256:82a9068d9cb15eb2d99ecf39f8d56b4ed9f931a77a3622a0de747465fd2a7b96",
-                "sha256:7ac6378ae364d8e5e5260c7224ea4a1965cb6f4719f15d0552349d0b0cc93953",
-                "sha256:5a952d4af7de5f78dfb3206dbc352717890b37d447f0bbd4b5969b3c8bb713af",
-                "sha256:b25c7720c495048ed658086a29925ab485ac7ececf1b346f2b459e5431d85016",
-                "sha256:528b0b811b6260a79222b055664b82093d01f35fe5c82521d8659cb2b28b8044",
-                "sha256:46ace48789865a89992419205024ae451d577876f9919fbb0f22f71189822dea",
-                "sha256:5436ca0ed752bb05a399fc07dc86dc23c756db523a3b7d5da46a457eacf4c4b5",
-                "sha256:f5e7d41d924a1d5274060c467539ee0c4f3bab318c1671ad65abd91f6b637baf",
-                "sha256:a8c12f3184c7cad8f66cae6c945d2c97e598b0cb7afd655a5b9471475e67f30e",
-                "sha256:756fc336a29c551b02252685f01bc87116bc9b04bbd02c1a6b8a96b3c6ad713b",
-                "sha256:cf790e61c2af0278f39dcedad9a22532bf81fb029c2cd73b1ceba7bea062c908",
-                "sha256:44c9cf24e63576244c13265ef0786b56d6751f5fb722225ecc021d6ecf91b4d2",
-                "sha256:ef1a36a16e72b6689ce0a6c7fc6bd88837d8fef4590b16bd72817644ae1f414d",
-                "sha256:3a4cdb9ca87c099d8ba5eb91cb8f000b60c21f8c1b50c75e04e8777e903bd278",
-                "sha256:f72bb19cece43483171264584bbaaf8b97717d9c0f244d1ef4a51df1cdb34085",
-                "sha256:c77e29243a79e376a1b51d71a13df4a61bc54fd4d9597ce3790b8d82ec6eb44d",
-                "sha256:8adda6583ba438a4c70693374e10b60168663ffa6564c5c75d3c7a9055290964"
+                "sha256:2e8be4c46083ced9d9bc9ff4d77f31bfcd3e7486613f6138c5aa302d33ea54ed",
+                "sha256:4634dd3bbb68d0c7e5e4bca7571369d53c497b3300d9d678f939038e1b1231ee",
+                "sha256:25825c61688fc95e09d6be19e513e925cb4f08aae4d7a7c38a1fa75e0e4c22bd",
+                "sha256:9e6d6f0bca955923b515f8b5631c4c4f43aa152763852284cbefc89bd544069e",
+                "sha256:6eef1d7eff9e6fa1029f7a62504f88b2b0afce89ced5c95d3a4cf1c2faef1231",
+                "sha256:040eecbc37aa5bd007108388fab6c42b2a01b964c4feac26bdffc8fe8af6c110",
+                "sha256:53988a8cf76c3fb74a759e77b1c2f55ab36880d57c6e7d0d59ad28743a2535fe",
+                "sha256:d51673140330c660e68c182e14164ddba47810dca873bbd28662f31d7d8c0185",
+                "sha256:2fe26e836a1803c7414613c376fe29fc4ae0e5145e3813e1db1854cb05c91a3c",
+                "sha256:15ad4d76bddfd98bf9e48263c70f6603e96d823c5a5c0c842646e9871be72c64",
+                "sha256:7910089093296b5c8f683965044f553b0c5c9c2dbf310a219db76c6e793fea55",
+                "sha256:a19b96f77763ddf0249420438ebfc4d9a470daeb26f6614366d913ff520fa29b",
+                "sha256:b53bc7b44b1115af50bd18d9671972603e5a4934e98dd3f4d671104c070e331d",
+                "sha256:4b6fa00885ec778154244b010acecb862d277e6652b87fcd85c0f4735d26451c",
+                "sha256:7aee5c0750584946fde40da70f0b28fe769f85182f1171acef18a35fd8ecd221"
             ],
-            "version": "==2.3.10"
+            "version": "==3.0.1"
         },
         "appdirs": {
             "hashes": [
@@ -68,6 +63,13 @@
             ],
             "version": "==2.0.0"
         },
+        "attrs": {
+            "hashes": [
+                "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450",
+                "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"
+            ],
+            "version": "==17.4.0"
+        },
         "beautifulsoup4": {
             "hashes": [
                 "sha256:7015e76bf32f1f574636c4288399a6de66ce08fb7b2457f628a8d70c0fbabb11",
diff --git a/src/cogs/booru.py b/src/cogs/booru.py
index 42009b9..a22e9c2 100644
--- a/src/cogs/booru.py
+++ b/src/cogs/booru.py
@@ -1,1661 +1,1661 @@
-import asyncio
-import json
-import re
-import sys
-import traceback as tb
-from contextlib import suppress
-from datetime import datetime as dt
-from datetime import timedelta as td
-from fractions import gcd
-import copy
-
-import discord as d
-from discord import errors as err
-from discord import reaction
-from discord.ext import commands as cmds
-from discord.ext.commands import errors as errext
-
-from cogs import tools
-from misc import exceptions as exc
-from misc import checks
-from utils import utils as u
-from utils import formatter, scraper
-
-
-class MsG:
-
-    def __init__(self, bot):
-        self.bot = bot
-        self.LIMIT = 100
-        self.HISTORY_LIMIT = 150
-        self.RATE_LIMIT = u.RATE_LIMIT
-        self.reversiqueue = asyncio.Queue()
-        self.heartqueue = asyncio.Queue()
-        self.reversifying = False
-        self.updating = False
-        self.hearting = False
-
-        time = (dt.utcnow() - td(days=29)).strftime('%d/%m/%Y/%H:%M:%S')
-        self.suggested = u.setdefault('cogs/suggested.pkl', 7)
-        # self.suggested = u.setdefault('cogs/suggested.pkl', {'last_update': 'test', 'tags': {}, 'total': 1})
-        print(self.suggested)
-        self.favorites = u.setdefault('cogs/favorites.pkl', {})
-        self.blacklists = u.setdefault(
-            'cogs/blacklists.pkl', {'global_blacklist': set(), 'guild_blacklist': {}, 'user_blacklist': {}})
-        self.aliases = u.setdefault('cogs/aliases.pkl', {})
-
-        if not self.hearting:
-            self.hearting = True
-            self.bot.loop.create_task(self._send_hearts())
-            print('STARTED : hearting')
-        if u.tasks['auto_rev']:
-            for channel in u.tasks['auto_rev']:
-                temp = self.bot.get_channel(channel)
-                self.bot.loop.create_task(self.queue_for_reversification(temp))
-                print('STARTED : auto-reversifying in #{}'.format(temp.name))
-            self.reversifying = True
-            self.bot.loop.create_task(self._reversify())
-        if u.tasks['auto_hrt']:
-            for channel in u.tasks['auto_hrt']:
-                temp = self.bot.get_channel(channel)
-                self.bot.loop.create_task(self.queue_for_hearts(channel=temp))
-                print(f'STARTED : auto-hearting in #{temp.name}')
-        # if not self.updating:
-        #     self.updating = True
-        #     self.bot.loop.create_task(self._update_suggested())
-
-    async def _update_suggested(self):
-        while self.updating:
-            print('Checking for tag updates...')
-            print(self.suggested)
-
-            time = dt.utcnow()
-            last_update = dt.strptime(self.suggested['last_update'], '%d/%m/%Y/%H:%M:%S')
-            delta = time - last_update
-            print(delta.days)
-
-            if delta.days < 30:
-                print('Up to date.')
-            else:
-                page = 1
-                pages = len(list(self.suggested['tags'].keys()))
-
-                print(f'Last updated: {self.suggested["last_update"]}')
-                print('Updating tags...')
-
-                content = await u.fetch('https://e621.net/tag/index.json', params={'order': 'count', 'limit': 500, 'page': page}, json=True)
-                while content:
-                    for tag in content:
-                        self.suggested['tags'][tag['name']] = tag['count']
-                        self.suggested['total'] += tag['count']
-                    print(f'    UPDATED : PAGE {page} / {pages}', end='\r')
-
-                    page += 1
-                    content = await u.fetch('https://e621.net/tag/index.json', params={'order': 'count', 'limit': 500, 'page': page}, json=True)
-
-                u.dump(self.suggested, 'cogs/suggested.pkl')
-                self.suggested['last_update'] = time.strftime('%d/%m/%Y/%H:%M:%S')
-
-                print('\nFinished updating tags.')
-
-            await asyncio.sleep(24 * 60 * 60)
-
-    def _get_favorites(self, ctx, args):
-        if '-f' in args or '-favs' in args or '-faves' in args or '-favorites' in args:
-            if self.favorites.get(ctx.author.id, {}).get('tags', set()):
-                args = ['~{}'.format(tag)
-                        for tag in self.favorites[ctx.author.id]['tags']]
-            else:
-                raise exc.FavoritesNotFound
-
-        return args
-
-    def _get_score(self, score):
-        if score < 0:
-            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/pouting-face_1f621.png'
-        elif score == 0:
-            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/mozilla/36/pile-of-poo_1f4a9.png'
-        elif 10 > score > 0:
-            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/white-medium-star_2b50.png'
-        elif 50 > score >= 10:
-            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/glowing-star_1f31f.png'
-        elif 100 > score >= 50:
-            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/dizzy-symbol_1f4ab.png'
-        elif score >= 100:
-            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/sparkles_2728.png'
-        return None
-
-    async def _send_hearts(self):
-        while self.hearting:
-            temp = await self.heartqueue.get()
-
-            if isinstance(temp[1], d.Embed):
-                await temp[0].send(embed=temp[1])
-
-                await asyncio.sleep(self.RATE_LIMIT)
-            elif isinstance(temp[1], d.Message):
-                for match in re.finditer('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', temp[1].content):
-                    await temp[0].send(match)
-
-                    await asyncio.sleep(self.RATE_LIMIT)
-
-                for attachment in temp[1].attachments:
-                    await temp[0].send(attachment.url)
-                    await asyncio.sleep(self.RATE_LIMIT)
-
-        print('STOPPED : hearting')
-
-    async def queue_for_hearts(self, *, message=None, send=None, channel=None, reaction=True, timeout=60 * 60):
-        def on_reaction(reaction, user):
-            if reaction.emoji == '\N{HEAVY BLACK HEART}' and reaction.message.id == message.id and not user.bot:
-                raise exc.Save(user)
-            return False
-        def on_reaction_channel(reaction, user):
-            if reaction.message.channel.id == channel.id and not user.bot:
-                if reaction.emoji == '\N{OCTAGONAL SIGN}' and user.permissions_in(reaction.message.channel).administrator:
-                    raise exc.Abort
-                if reaction.emoji == '\N{HEAVY BLACK HEART}' and (re.search('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', reaction.message.content) or reaction.message.attachments):
-                    raise exc.Save(user, reaction.message)
-            return False
-
-        if message:
-            try:
-                if reaction:
-                    await message.add_reaction('\N{HEAVY BLACK HEART}')
-                    await asyncio.sleep(1)
-
-                while self.hearting:
-                    try:
-                        await self.bot.wait_for('reaction_add', check=on_reaction, timeout=timeout)
-
-                    except exc.Save as e:
-                        await self.heartqueue.put((e.user, send if send else message))
-
-            except asyncio.TimeoutError:
-                await message.add_reaction('\N{WHITE HEAVY CHECK MARK}')
-        else:
-            try:
-                while self.hearting:
-                    try:
-                        await self.bot.wait_for('reaction_add', check=on_reaction_channel)
-
-                    except exc.Save as e:
-                        await self.heartqueue.put((e.user, message))
-
-            except exc.Abort:
-                u.tasks['auto_hrt'].remove(channel.id)
-                u.dump(u.tasks, 'cogs/tasks.pkl')
-                print('STOPPED : auto-hearting in #{}'.format(channel.name))
-                await channel.send('**Stopped queueing messages for hearting in** {}'.format(channel.mention), delete_after=5)
-
-    @cmds.command(name='autoheart', aliases=['autohrt'])
-    @cmds.has_permissions(administrator=True)
-    async def auto_heart(self, ctx):
-        try:
-            if ctx.channel.id not in u.tasks['auto_hrt']:
-                u.tasks['auto_hrt'].append(ctx.channel.id)
-                u.dump(u.tasks, 'cogs/tasks.pkl')
-                self.bot.loop.create_task(self.queue_for_hearts(channel=ctx.channel))
-                print('STARTED : auto-hearting in #{}'.format(ctx.channel.name))
-                await ctx.send('**Auto-hearting all messages in {}**'.format(ctx.channel.mention), delete_after=5)
-            else:
-                raise exc.Exists
-
-        except exc.Exists:
-            message = await ctx.send('**Already auto-hearting in {}.** React with \N{OCTAGONAL SIGN} to stop.'.format(ctx.channel.mention))
-            await message.add_reaction('\N{OCTAGONAL SIGN}')
-
-    # @cmds.command()
-    # async def auto_post(self, ctx):
-    #     try:
-    #         if ctx.channel.id not in u.tasks['auto_post']:
-    #             u.tasks['auto_post'].append(ctx.channel.id)
-    #             u.dump(u.tasks, 'cogs/tasks.pkl')
-    #             self.bot.loop.create_task(self.queue_for_posting(ctx.channel))
-    #             if not self.posting:
-    #                 self.bot.loop.create_task(self._post())
-    #                 self.posting = True
-    #
-    #             print('STARTED : auto-posting in #{}'.format(ctx.channel.name))
-    #             await ctx.send('**Auto-posting all images in {}**'.format(ctx.channel.mention), delete_after=5)
-    #         else:
-    #             raise exc.Exists
-    #
-    #     except exc.Exists:
-    #         await ctx.send('**Already auto-posting in {}.** Type `stop` to stop.'.format(ctx.channel.mention), delete_after=7)
-    #         await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @cmds.group(aliases=['tag', 't'], brief='(G) Get info on tags', description='Group command for obtaining info on tags\n\nUsage:\n\{p\}tag \{flag\} \{tag(s)\}')
-    async def tags(self, ctx):
-        pass
-
-    # Tag search
-    @tags.command(name='related', aliases=['relate', 'rel', 'r'], brief='(tags) Search for related tags', description='Return related tags for given tag(s)\n\nExample:\n\{p\}tag related wolf')
-    async def _tags_related(self, ctx, *args):
-        kwargs = u.get_kwargs(ctx, args)
-        dest, tags = kwargs['destination'], kwargs['remaining']
-        related = []
-        c = 0
-
-        await dest.trigger_typing()
-
-        for tag in tags:
-            try:
-                tag_request = await u.fetch('https://e621.net/tag/related.json', params={'tags': tag}, json=True)
-                for rel in tag_request.get(tag, []):
-                    related.append(rel[0])
-
-                if related:
-                    await dest.send('`{}` **related tags:**\n```\n{}```'.format(tag, formatter.tostring(related)))
-                else:
-                    await ctx.send(f'**No related tags found for:** `{tag}`', delete_after=7)
-
-                related.clear()
-                c += 1
-
-            finally:
-                await asyncio.sleep(self.RATE_LIMIT)
-
-        if not c:
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    # Tag aliases
-    @tags.command(name='aliases', aliases=['alias', 'als', 'a'], brief='(tags) Search for tag aliases', description='Return aliases for given tag(s)\n\nExample:\n\{p\}tag alias wolf')
-    async def _tags_aliases(self, ctx, *args):
-        kwargs = u.get_kwargs(ctx, args)
-        dest, tags = kwargs['destination'], kwargs['remaining']
-        aliases = []
-        c = 0
-
-        await dest.trigger_typing()
-
-        for tag in tags:
-            try:
-                alias_request = await u.fetch('https://e621.net/tag_alias/index.json', params={'aliased_to': tag, 'approved': 'true'}, json=True)
-                for dic in alias_request:
-                    aliases.append(dic['name'])
-
-                if aliases:
-                    await dest.send('`{}` **aliases:**\n```\n{}```'.format(tag, formatter.tostring(aliases)))
-                else:
-                    await ctx.send(f'**No aliases found for:** `{tag}`', delete_after=7)
-
-                aliases.clear()
-                c += 1
-
-            finally:
-                await asyncio.sleep(self.RATE_LIMIT)
-
-        if not c:
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @cmds.group(aliases=['g'], brief='(G) Get e621 elements', description='Group command for obtaining various elements like post info\n\nUsage:\n\{p\}get \{flag\} \{args\}')
-    async def get(self, ctx):
-        if not ctx.invoked_subcommand:
-            await ctx.send('**Use a flag to get items.**\n*Type* `{}help get` *for more info.*'.format(ctx.prefix), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @get.command(name='info', aliases=['i'], brief='(get) Get info from post', description='Return info for given post URL or ID\n\nExample:\n\{p\}get info 1145042')
-    async def _get_info(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, posts = kwargs['destination'], kwargs['remaining']
-
-            if not posts:
-                raise exc.MissingArgument
-
-            for ident in posts:
-                try:
-                    await dest.trigger_typing()
-
-                    ident = ident if not ident.isdigit() else re.search(
-                        'show/([0-9]+)', ident).group(1)
-                    post = await u.fetch('https://e621.net/post/show.json', params={'id': ident}, json=True)
-
-                    embed = d.Embed(
-                        title=', '.join(post['artist']), url=f'https://e621.net/post/show/{post["id"]}', color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
-                    embed.set_thumbnail(url=post['file_url'])
-                    embed.set_author(name=f'{post["width"]} x {post["height"]}',
-                                     url=f'https://e621.net/post?tags=ratio:{post["width"]/post["height"]:.2f}', icon_url=ctx.author.avatar_url)
-                    embed.set_footer(text=post['score'],
-                                     icon_url=self._get_score(post['score']))
-
-                # except
-
-                finally:
-                    await asyncio.sleep(self.RATE_LIMIT)
-
-        except exc.MissingArgument:
-            await ctx.send('**Invalid url**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @get.command(name='image', aliases=['img'], brief='(get) Get direct image from post', description='Return direct image URL for given post\n\nExample:\n\{p\}get image 1145042')
-    async def _get_image(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, urls = kwargs['destination'], kwargs['remaining']
-            c = 0
-
-            if not urls:
-                raise exc.MissingArgument
-
-            for url in urls:
-                try:
-                    await dest.trigger_typing()
-
-                    await dest.send(await scraper.get_image(url))
-
-                    c += 1
-
-                # except
-                    # await ctx.send(f'**No aliases found for:** `{tag}`', delete_after=7)
-
-                finally:
-                    await asyncio.sleep(self.RATE_LIMIT)
-
-            if not c:
-                await ctx.message.add_reaction('\N{CROSS MARK}')
-
-        except exc.MissingArgument:
-            await ctx.send('**Invalid url or file**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @get.command(name='pool', aliases=['p'], brief='(get) Get pool from query', description='Return pool info for given query\n\nExample:\n\{p\}get pool 1145042')
-    async def _get_pool(self, ctx, *args):
-        def on_reaction(reaction, user):
-            if reaction.emoji == '\N{OCTAGONAL SIGN}' and reaction.message.id == ctx.message.id and user is ctx.author:
-                raise exc.Abort(match)
-            return False
-
-        def on_message(msg):
-            return msg.content.isdigit() and int(msg.content) <= len(pools) and int(msg.content) > 0 and msg.author is ctx.author and msg.channel is ctx.channel
-
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, query = kwargs['destination'], kwargs['remaining']
-            ident = None
-
-            await dest.trigger_typing()
-
-            pools = []
-            pool_request = await u.fetch('https://e621.net/pool/index.json', params={'query': ' '.join(query)}, json=True)
-            if len(pool_request) > 1:
-                for pool in pool_request:
-                    pools.append(pool['name'])
-                match = await ctx.send('**Multiple pools found for `{}`.** Type the number of the correct match\n```\n{}```'.format(' '.join(query), '\n'.join(['{} {}'.format(c, elem) for c, elem in enumerate(pools, 1)])))
-
-                await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
-                done, pending = await asyncio.wait([self.bot.wait_for('reaction_add', check=on_reaction, timeout=60),
-                                                    self.bot.wait_for('reaction_remove', check=on_reaction, timeout=60), self.bot.wait_for('message', check=on_message, timeout=60)], return_when=asyncio.FIRST_COMPLETED)
-                for future in done:
-                    selection = future.result()
-
-                await match.delete()
-                tempool = [pool for pool in pool_request if pool['name']
-                           == pools[int(selection.content) - 1]][0]
-                await selection.delete()
-            elif pool_request:
-                tempool = pool_request[0]
-            else:
-                raise exc.NotFound
-
-            await ctx.send(f'**{tempool["name"]}**\nhttps://e621.net/pool/show/{tempool["id"]}')
-
-        except exc.Abort as e:
-            await e.message.edit(content='\N{NO ENTRY SIGN}', delete_after=7)
-
-    # Reverse image searches a linked image using the public iqdb
-    @cmds.command(name='reverse', aliases=['rev', 'ris'], brief='Reverse image search from e621', description='NSFW\nReverse-search an image with given URL')
-    async def reverse(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, urls = kwargs['destination'], kwargs['remaining']
-            c = 0
-
-            if not urls and not ctx.message.attachments:
-                raise exc.MissingArgument
-
-            for attachment in ctx.message.attachments:
-                urls.append(attachment.url)
-
-            for url in urls:
-                try:
-                    await dest.trigger_typing()
-
-                    post = await scraper.get_post(url)
-
-                    embed = d.Embed(
-                        title=', '.join(post['artist']), url=f'https://e621.net/post/show/{post["id"]}', color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
-                    embed.set_image(url=post['file_url'])
-                    embed.set_author(name=f'{post["width"]} x {post["height"]}',
-                                     url=f'https://e621.net/post?tags=ratio:{post["width"]/post["height"]:.2f}', icon_url=ctx.author.avatar_url)
-                    embed.set_footer(text=post['score'],
-                                     icon_url=self._get_score(post['score']))
-
-                    await dest.send('**Probable match**', embed=embed)
-
-                    c += 1
-
-                except exc.MatchError as e:
-                    await ctx.send('**No probable match for:** `{}`'.format(e), delete_after=7)
-
-            if not c:
-                await ctx.message.add_reaction('\N{CROSS MARK}')
-
-        except exc.MissingArgument:
-            await ctx.send('**Invalid url or file.** Be sure the link directs to an image file', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.SizeError as e:
-            await ctx.send(f'`{e}` **too large.** Maximum is 8 MB', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @cmds.command(name='reversify', aliases=['revify', 'risify', 'rify'])
-    async def reversify(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args, limit=self.HISTORY_LIMIT / 5)
-            dest, remove, limit = kwargs['destination'], kwargs['remove'], kwargs['limit']
-            links = {}
-            c = 0
-
-            if not ctx.author.permissions_in(ctx.channel).manage_messages:
-                dest = ctx.author
-
-            async for message in ctx.channel.history(limit=self.HISTORY_LIMIT * limit):
-                if c >= limit:
-                    break
-                if message.author.id != self.bot.user.id and (re.search('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', message.content) is not None or message.embeds or message.attachments):
-                    links[message] = []
-                    for match in re.finditer('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', message.content):
-                        links[message].append(match.group(0))
-                    for embed in message.embeds:
-                        if embed.image.url is not d.Embed.Empty:
-                            links[message].append(embed.image.url)
-                    for attachment in message.attachments:
-                        links[message].append(attachment.url)
-
-                    await message.add_reaction('\N{HOURGLASS WITH FLOWING SAND}')
-                    c += 1
-
-            if not links:
-                raise exc.NotFound
-
-            n = 1
-            for message, urls in links.items():
-                for url in urls:
-                    try:
-                        await dest.trigger_typing()
-
-                        post = await scraper.get_post(url)
-
-                        embed = d.Embed(
-                            title=', '.join(post['artist']), url=f'https://e621.net/post/show/{post["id"]}', color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
-                        embed.set_image(url=post['file_url'])
-                        embed.set_author(name=f'{post["width"]} x {post["height"]}',
-                                         url=f'https://e621.net/post?tags=ratio:{post["width"]/post["height"]:.2f}', icon_url=ctx.author.avatar_url)
-                        embed.set_footer(
-                            text=post['score'], icon_url=self._get_score(post['score']))
-
-                        await dest.send(f'**Probable match from** {message.author.display_name}', embed=embed)
-                        await message.add_reaction('\N{WHITE HEAVY CHECK MARK}')
-
-                        if remove:
-                            with suppress(err.NotFound):
-                                await message.delete()
-
-                    except exc.MatchError as e:
-                        await ctx.send('`{} / {}` **No probable match for:** `{}`'.format(n, len(links), e), delete_after=7)
-                        await message.add_reaction('\N{CROSS MARK}')
-                        c -= 1
-                    except exc.SizeError as e:
-                        await ctx.send(f'`{e}` **too large.** Maximum is 8 MB', delete_after=7)
-                        await message.add_reaction('\N{CROSS MARK}')
-                        c -= 1
-
-                    finally:
-                        n += 1
-
-            if c <= 0:
-                await ctx.message.add_reaction('\N{CROSS MARK}')
-
-        except exc.NotFound:
-            await ctx.send('**No matches found**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.BoundsError as e:
-            await ctx.send('`{}` **invalid limit.** Query limited to 30'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    async def _reversify(self):
-        while self.reversifying:
-            message = await self.reversiqueue.get()
-            urls = []
-
-            for match in re.finditer('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', message.content):
-                urls.append(match.group(0))
-            for embed in message.embeds:
-                if embed.image.url is not d.Embed.Empty:
-                    urls.append(embed.image.url)
-            for attachment in message.attachments:
-                urls.append(attachment.url)
-
-            for url in urls:
-                try:
-                    await message.channel.trigger_typing()
-
-                    post = await scraper.get_post(url)
-
-                    embed = d.Embed(
-                        title=', '.join(post['artist']), url=f'https://e621.net/post/show/{post["id"]}', color=message.channel.guild.me.color if isinstance(message.channel, d.TextChannel) else u.color)
-                    embed.set_image(url=post['file_url'])
-                    embed.set_author(name=f'{post["width"]} x {post["height"]}',
-                                     url=f'https://e621.net/post?tags=ratio:{post["width"]/post["height"]:.2f}', icon_url=message.author.avatar_url)
-                    embed.set_footer(text=post['score'],
-                                     icon_url=self._get_score(post['score']))
-
-                    await message.channel.send('**Probable match from** {}'.format(message.author.display_name), embed=embed)
-
-                    await message.add_reaction('\N{WHITE HEAVY CHECK MARK}')
-
-                    await asyncio.sleep(self.RATE_LIMIT)
-
-                    with suppress(err.NotFound):
-                        await message.delete()
-
-                except exc.MatchError as e:
-                    await message.channel.send('**No probable match for:** `{}`'.format(e), delete_after=7)
-                    await message.add_reaction('\N{CROSS MARK}')
-                except exc.SizeError as e:
-                    await message.channel.send(f'`{e}` **too large.** Maximum is 8 MB', delete_after=7)
-                    await message.add_reaction('\N{CROSS MARK}')
-                except Exception:
-                    await message.channel.send(f'**An unknown error occurred.**', delete_after=7)
-                    await message.add_reaction('\N{WARNING SIGN}')
-
-        print('STOPPED : reversifying')
-
-    async def queue_for_reversification(self, channel):
-        def check(msg):
-            if 'stop r' in msg.content.lower() and msg.channel is channel and msg.author.guild_permissions.administrator:
-                raise exc.Abort
-            elif msg.channel is channel and msg.author.id != self.bot.user.id and (re.search('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', msg.content) is not None or msg.attachments or msg.embeds):
-                return True
-            return False
-
-        try:
-            while self.reversifying:
-                message = await self.bot.wait_for('message', check=check)
-                await self.reversiqueue.put(message)
-                await message.add_reaction('\N{HOURGLASS WITH FLOWING SAND}')
-
-        except exc.Abort:
-            u.tasks['auto_rev'].remove(channel.id)
-            u.dump(u.tasks, 'cogs/tasks.pkl')
-            if not u.tasks['auto_rev']:
-                self.reversifying = False
-            print('STOPPED : reversifying #{}'.format(channel.name))
-            await channel.send('**Stopped queueing messages for reversification in** {}'.format(channel.mention), delete_after=5)
-
-    @cmds.command(name='autoreversify', aliases=['autorev'])
-    @cmds.has_permissions(manage_channels=True)
-    async def auto_reversify(self, ctx):
-        if ctx.channel.id not in u.tasks['auto_rev']:
-            u.tasks['auto_rev'].append(ctx.channel.id)
-            u.dump(u.tasks, 'cogs/tasks.pkl')
-            self.bot.loop.create_task(
-                self.queue_for_reversification(ctx.channel))
-            if not self.reversifying:
-                self.bot.loop.create_task(self._reversify())
-                self.reversifying = True
-
-            print('STARTED : auto-reversifying in #{}'.format(ctx.channel.name))
-            await ctx.send('**Auto-reversifying all images in** {}'.format(ctx.channel.mention), delete_after=5)
-        else:
-            await ctx.send('**Already auto-reversifying in {}.** Type `stop r(eversifying)` to stop.'.format(ctx.channel.mention), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    async def _get_pool(self, ctx, *, destination, booru='e621', query=[]):
-        def on_reaction(reaction, user):
-            if reaction.emoji == '\N{OCTAGONAL SIGN}' and reaction.message.id == ctx.message.id and user is ctx.author:
-                raise exc.Abort(match)
-            return False
-
-        def on_message(msg):
-            return msg.content.isdigit() and int(msg.content) <= len(pools) and int(msg.content) > 0 and msg.author is ctx.author and msg.channel is ctx.channel
-
-        posts = {}
-        pool = {}
-
-        try:
-            pools = []
-            pool_request = await u.fetch('https://{}.net/pool/index.json'.format(booru), params={'query': ' '.join(query)}, json=True)
-            if len(pool_request) > 1:
-                for pool in pool_request:
-                    pools.append(pool['name'])
-                match = await ctx.send('**Multiple pools found for `{}`.** Type the number of the correct match.\n```\n{}```'.format(' '.join(query), '\n'.join(['{} {}'.format(c, elem) for c, elem in enumerate(pools, 1)])))
-
-                await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
-                done, pending = await asyncio.wait([self.bot.wait_for('reaction_add', check=on_reaction, timeout=60),
-                                                    self.bot.wait_for('reaction_remove', check=on_reaction, timeout=60), self.bot.wait_for('message', check=on_message, timeout=60)], return_when=asyncio.FIRST_COMPLETED)
-                for future in done:
-                    selection = future.result()
-
-                await match.delete()
-                tempool = [pool for pool in pool_request if pool['name']
-                           == pools[int(selection.content) - 1]][0]
-                await selection.delete()
-                pool = {'name': tempool['name'], 'id': tempool['id']}
-
-                await destination.trigger_typing()
-            elif pool_request:
-                tempool = pool_request[0]
-                pool = {'name': pool_request[0]
-                        ['name'], 'id': pool_request[0]['id']}
-            else:
-                raise exc.NotFound
-
-            page = 1
-            while len(posts) < tempool['post_count']:
-                posts_request = await u.fetch('https://{}.net/pool/show.json'.format(booru), params={'id': tempool['id'], 'page': page}, json=True)
-                for post in posts_request['posts']:
-                    posts[post['id']] = {'artist': ', '.join(
-                        post['artist']), 'file_url': post['file_url'], 'score': post['score']}
-                page += 1
-
-            return pool, posts
-
-        except exc.Abort as e:
-            await e.message.edit(content='\N{NO ENTRY SIGN}')
-            raise exc.Continue
-
-    # Messy code that checks image limit and tags in blacklists
-    async def _get_posts(self, ctx, *, booru='e621', tags=[], limit=1, previous={}):
-        guild = ctx.guild if isinstance(
-            ctx.guild, d.Guild) else ctx.channel
-
-        blacklist = set()
-        # Creates temp blacklist based on context
-        for bl in (self.blacklists['global_blacklist'], self.blacklists['guild_blacklist'].get(guild.id, {}).get(ctx.channel.id, set()), self.blacklists['user_blacklist'].get(ctx.author.id, set())):
-            for tag in bl:
-                blacklist.update([tag] + list(self.aliases[tag]))
-        # Checks for, assigns, and removes first order in tags if possible
-        order = [tag for tag in tags if 'order:' in tag]
-        if order:
-            order = order[0]
-            tags.remove(order)
-        else:
-            order = 'order:random'
-        # Checks if tags are in local blacklists
-        if tags:
-            if (len(tags) > 5 and booru == 'e621') or (len(tags) > 4 and booru == 'e926'):
-                raise exc.TagBoundsError(formatter.tostring(tags[5:]))
-            for tag in tags:
-                if tag == 'swf' or tag == 'webm' or tag in blacklist:
-                    raise exc.TagBlacklisted(tag)
-
-        # Checks for blacklisted tags in endpoint blacklists - try/except is for continuing the parent loop
-        posts = {}
-        temposts = len(posts)
-        empty = 0
-        c = 0
-        while len(posts) < limit:
-            if c == limit * 5 + (self.LIMIT / 5):
-                raise exc.Timeout
-            request = await u.fetch('https://{}.net/post/index.json'.format(booru), params={'tags': ','.join([order] + tags), 'limit': int(self.LIMIT * limit)}, json=True)
-            if len(request) == 0:
-                raise exc.NotFound(formatter.tostring(tags))
-            if len(request) < limit:
-                limit = len(request)
-
-            for post in request:
-                if 'swf' in post['file_ext'] or 'webm' in post['file_ext']:
-                    continue
-                try:
-                    for tag in blacklist:
-                        if tag in post['tags']:
-                            raise exc.Continue
-                except exc.Continue:
-                    continue
-                if post['id'] not in posts.keys() and post['id'] not in previous.keys():
-                    posts[post['id']] = {'artist': ', '.join(
-                        post['artist']), 'file_url': post['file_url'], 'score': post['score']}
-                if len(posts) == limit:
-                    break
-
-            if len(posts) == temposts:
-                empty += 1
-                if empty == 5:
-                    break
-            else:
-                empty = 0
-                temposts = len(posts)
-                c += 1
-
-        if posts:
-            return posts, order
-        else:
-            raise exc.NotFound(formatter.tostring(tags))
-
-    # Creates reaction-based paginator for linked pools
-    @cmds.command(name='poolpage', aliases=['poolp', 'pp', 'e621pp', 'e6pp', '6pp'], brief='e621 pool paginator', description='e621 | NSFW\nShow pools in a page format')
-    async def pool_paginator(self, ctx, *args):
-        def on_reaction(reaction, user):
-            if reaction.emoji == '\N{OCTAGONAL SIGN}' and reaction.message.id == ctx.message.id and (user is ctx.author or user.permissions_in(reaction.message.channel).manage_messages):
-                raise exc.Abort
-            elif reaction.emoji == '\N{HEAVY BLACK HEART}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.Save
-            elif reaction.emoji == '\N{LEFTWARDS BLACK ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.Left
-            elif reaction.emoji == '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.GoTo
-            elif reaction.emoji == '\N{BLACK RIGHTWARDS ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.Right
-            return False
-
-        def on_message(msg):
-            return msg.content.isdigit() and 0 <= int(msg.content) <= len(posts) and msg.author is ctx.author and msg.channel is ctx.channel
-
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, query = kwargs['destination'], kwargs['remaining']
-            hearted = {}
-            c = 1
-
-            await dest.trigger_typing()
-
-            pool, posts = await self._get_pool(ctx, destination=dest, booru='e621', query=query)
-            keys = list(posts.keys())
-            values = list(posts.values())
-
-            embed = d.Embed(
-                title=values[c - 1]['artist'], url='https://e621.net/post/show/{}'.format(keys[c - 1]), color=dest.me.color if isinstance(dest.channel, d.TextChannel) else u.color)
-            embed.set_image(url=values[c - 1]['file_url'])
-            embed.set_author(name=pool['name'],
-                             url='https://e621.net/pool/show?id={}'.format(pool['id']), icon_url=ctx.author.avatar_url)
-            embed.set_footer(text='{} / {}'.format(c, len(posts)),
-                             icon_url=self._get_score(values[c - 1]['score']))
-
-            paginator = await dest.send(embed=embed)
-
-            for emoji in ('\N{HEAVY BLACK HEART}', '\N{LEFTWARDS BLACK ARROW}', '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}', '\N{BLACK RIGHTWARDS ARROW}'):
-                await paginator.add_reaction(emoji)
-            await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
-            await asyncio.sleep(1)
-
-            while not self.bot.is_closed():
-                try:
-                    await asyncio.gather(*[self.bot.wait_for('reaction_add', check=on_reaction, timeout=7 * 60),
-                                           self.bot.wait_for('reaction_remove', check=on_reaction, timeout=7 * 60)])
-
-                except exc.Save:
-                    if keys[c - 1] not in hearted:
-                        hearted[keys[c - 1]] = copy.deepcopy(embed)
-
-                        await paginator.edit(content='\N{HEAVY BLACK HEART}')
-                    else:
-                        del hearted[keys[c - 1]]
-
-                        await paginator.edit(content='\N{BROKEN HEART}')
-
-                except exc.Left:
-                    if c > 1:
-                        c -= 1
-                        embed.title = values[c - 1]['artist']
-                        embed.url = 'https://e621.net/post/show/{}'.format(
-                            keys[c - 1])
-                        embed.set_footer(text='{} / {}'.format(c, len(posts)),
-                                         icon_url=self._get_score(values[c - 1]['score']))
-                        embed.set_image(url=values[c - 1]['file_url'])
-
-                        await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
-                    else:
-                        await paginator.edit(content='\N{BLACK RIGHTWARDS ARROW}')
-
-                except exc.GoTo:
-                    await paginator.edit(content='\N{INPUT SYMBOL FOR NUMBERS}')
-                    number = await self.bot.wait_for('message', check=on_message, timeout=7 * 60)
-
-                    if int(number.content) != 0:
-                        c = int(number.content)
-
-                        embed.title = values[c - 1]['artist']
-                        embed.url = 'https://e621.net/post/show/{}'.format(
-                            keys[c - 1])
-                        embed.set_footer(text='{} / {}'.format(c, len(posts)),
-                                         icon_url=self._get_score(values[c - 1]['score']))
-                        embed.set_image(url=values[c - 1]['file_url'])
-
-                    await number.delete()
-
-                    await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
-
-                except exc.Right:
-                    if c < len(keys):
-                        c += 1
-                        embed.title = values[c - 1]['artist']
-                        embed.url = 'https://e621.net/post/show/{}'.format(
-                            keys[c - 1])
-                        embed.set_footer(text='{} / {}'.format(c, len(posts)),
-                                         icon_url=self._get_score(values[c - 1]['score']))
-                        embed.set_image(url=values[c - 1]['file_url'])
-
-                        await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
-                    else:
-                        await paginator.edit(content='\N{LEFTWARDS BLACK ARROW}')
-
-        except exc.Abort:
-            try:
-                await paginator.edit(content='\N{WHITE HEAVY CHECK MARK}')
-            except UnboundLocalError:
-                await dest.send('\N{WHITE HEAVY CHECK MARK}')
-        except asyncio.TimeoutError:
-            try:
-                await paginator.edit(content='\N{HOURGLASS}')
-            except UnboundLocalError:
-                await dest.send('\N{HOURGLASS}')
-        except exc.NotFound:
-            await ctx.send('**Pool not found**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.Timeout:
-            await ctx.send('**Request timed out**')
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.Continue:
-            pass
-
-        finally:
-            if hearted:
-                await ctx.message.add_reaction('\N{HOURGLASS WITH FLOWING SAND}')
-
-                n = 1
-                for embed in hearted.values():
-                    await asyncio.sleep(self.RATE_LIMIT)
-
-                    await ctx.author.send(content=f'`{n} / {len(hearted)}`', embed=embed)
-                    n += 1
-
-    @cmds.command(name='e621page', aliases=['e621p', 'e6p', '6p'])
-    @checks.is_nsfw()
-    async def e621_paginator(self, ctx, *args):
-        def on_reaction(reaction, user):
-            if reaction.emoji == '\N{OCTAGONAL SIGN}' and reaction.message.id == ctx.message.id and (user is ctx.author or user.permissions_in(reaction.message.channel).manage_messages):
-                raise exc.Abort
-            elif reaction.emoji == '\N{HEAVY BLACK HEART}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.Save
-            elif reaction.emoji == '\N{LEFTWARDS BLACK ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.Left
-            elif reaction.emoji == '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.GoTo
-            elif reaction.emoji == '\N{BLACK RIGHTWARDS ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.Right
-            return False
-
-        def on_message(msg):
-            return msg.content.isdigit() and 0 <= int(msg.content) <= len(posts) and msg.author is ctx.author and msg.channel is ctx.channel
-
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, tags = kwargs['destination'], kwargs['remaining']
-            limit = self.LIMIT / 5
-            hearted = {}
-            c = 1
-
-            tags = self._get_favorites(ctx, tags)
-
-            await ctx.trigger_typing()
-
-            posts, order = await self._get_posts(ctx, booru='e621', tags=tags, limit=limit)
-            keys = list(posts.keys())
-            values = list(posts.values())
-
-            embed = d.Embed(
-                title=values[c - 1]['artist'], url='https://e621.net/post/show/{}'.format(keys[c - 1]), color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
-            embed.set_image(url=values[c - 1]['file_url'])
-            embed.set_author(name=formatter.tostring(tags, order=order),
-                             url='https://e621.net/post?tags={}'.format(','.join(tags)), icon_url=ctx.author.avatar_url)
-            embed.set_footer(text=values[c - 1]['score'],
-                             icon_url=self._get_score(values[c - 1]['score']))
-
-            paginator = await dest.send(embed=embed)
-
-            for emoji in ('\N{HEAVY BLACK HEART}', '\N{LEFTWARDS BLACK ARROW}', '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}', '\N{BLACK RIGHTWARDS ARROW}'):
-                await paginator.add_reaction(emoji)
-            await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
-            await asyncio.sleep(1)
-
-            while not self.bot.is_closed():
-                try:
-                    await asyncio.gather(*[self.bot.wait_for('reaction_add', check=on_reaction, timeout=7 * 60),
-                                           self.bot.wait_for('reaction_remove', check=on_reaction, timeout=7 * 60)])
-
-                except exc.Save:
-                    if keys[c - 1] not in hearted.keys():
-                        hearted[keys[c - 1]] = copy.deepcopy(embed)
-
-                        await paginator.edit(content='\N{HEAVY BLACK HEART}')
-                    else:
-                        del hearted[keys[c - 1]]
-
-                        await paginator.edit(content='\N{BROKEN HEART}')
-
-                except exc.Left:
-                    if c > 1:
-                        c -= 1
-                        embed.title = values[c - 1]['artist']
-                        embed.url = 'https://e621.net/post/show/{}'.format(
-                            keys[c - 1])
-                        embed.set_footer(text=values[c - 1]['score'],
-                                         icon_url=self._get_score(values[c - 1]['score']))
-                        embed.set_image(url=values[c - 1]['file_url'])
-
-                        await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
-                    else:
-                        await paginator.edit(content='\N{BLACK RIGHTWARDS ARROW}')
-
-                except exc.GoTo:
-                    await paginator.edit(content=f'`{c} / {len(posts)}`')
-                    number = await self.bot.wait_for('message', check=on_message, timeout=7 * 60)
-
-                    if int(number.content) != 0:
-                        c = int(number.content)
-
-                        embed.title = values[c - 1]['artist']
-                        embed.url = 'https://e621.net/post/show/{}'.format(
-                            keys[c - 1])
-                        embed.set_footer(text=values[c - 1]['score'],
-                                         icon_url=self._get_score(values[c - 1]['score']))
-                        embed.set_image(url=values[c - 1]['file_url'])
-
-                    await number.delete()
-
-                    await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
-
-                except exc.Right:
-                    try:
-                        if c % limit == 0:
-                            await dest.trigger_typing()
-                            temposts, order = await self._get_posts(ctx, booru='e621', tags=tags, limit=limit, previous=posts)
-                            posts.update(temposts)
-
-                            keys = list(posts.keys())
-                            values = list(posts.values())
-
-                        if c < len(keys):
-                            c += 1
-                            embed.title = values[c - 1]['artist']
-                            embed.url = 'https://e621.net/post/show/{}'.format(
-                                keys[c - 1])
-                            embed.set_footer(text=values[c - 1]['score'],
-                                             icon_url=self._get_score(values[c - 1]['score']))
-                            embed.set_image(url=values[c - 1]['file_url'])
-
-                            await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
-                        else:
-                            await paginator.edit(content='\N{LEFTWARDS BLACK ARROW}')
-
-                    except exc.NotFound:
-                        await paginator.edit(content='\N{LEFTWARDS BLACK ARROW}')
-
-        except exc.Abort:
-            try:
-                await paginator.edit(content='\N{WHITE HEAVY CHECK MARK}')
-            except UnboundLocalError:
-                await dest.send('\N{HOURGLASS}')
-        except asyncio.TimeoutError:
-            try:
-                await paginator.edit(content='\N{HOURGLASS}')
-            except UnboundLocalError:
-                await dest.send('\N{HOURGLASS}')
-        except exc.NotFound as e:
-            await ctx.send('`{}` **not found**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.TagBlacklisted as e:
-            await ctx.send('\N{NO ENTRY SIGN} `{}` **blacklisted**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{NO ENTRY SIGN}')
-        except exc.TagBoundsError as e:
-            await ctx.send('`{}` **out of bounds.** Tags limited to 5.'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.FavoritesNotFound:
-            await ctx.send('**You have no favorite tags**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.Timeout:
-            await ctx.send('**Request timed out**')
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-        finally:
-            if hearted:
-                await ctx.message.add_reaction('\N{HOURGLASS WITH FLOWING SAND}')
-
-                n = 1
-                for embed in hearted.values():
-                    await asyncio.sleep(self.RATE_LIMIT)
-
-                    await ctx.author.send(content=f'`{n} / {len(hearted)}`', embed=embed)
-                    n += 1
-
-    # @e621_paginator.error
-    # async def e621_paginator_error(self, ctx, error):
-    #     if isinstance(error, exc.NSFW):
-    #         await ctx.send('\N{NO ENTRY} {} **is not an NSFW channel**'.format(ctx.channel.mention), delete_after=7)
-    #         await ctx.message.add_reaction('\N{NO ENTRY}')
-
-    @cmds.command(name='e926page', aliases=['e926p', 'e9p', '9p'])
-    async def e926_paginator(self, ctx, *args):
-        def on_reaction(reaction, user):
-            if reaction.emoji == '\N{OCTAGONAL SIGN}' and reaction.message.id == ctx.message.id and (user is ctx.author or user.permissions_in(reaction.message.channel).manage_messages):
-                raise exc.Abort
-            elif reaction.emoji == '\N{HEAVY BLACK HEART}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.Save
-            elif reaction.emoji == '\N{LEFTWARDS BLACK ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.Left
-            elif reaction.emoji == '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.GoTo
-            elif reaction.emoji == '\N{BLACK RIGHTWARDS ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
-                raise exc.Right
-            return False
-
-        def on_message(msg):
-            return msg.content.isdigit() and 0 <= int(msg.content) <= len(posts) and msg.author is ctx.author and msg.channel is ctx.channel
-
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, tags = kwargs['destination'], kwargs['remaining']
-            limit = self.LIMIT / 5
-            hearted = {}
-            c = 1
-
-            tags = self._get_favorites(ctx, tags)
-
-            await ctx.trigger_typing()
-
-            posts, order = await self._get_posts(ctx, booru='e926', tags=tags, limit=limit)
-            keys = list(posts.keys())
-            values = list(posts.values())
-
-            embed = d.Embed(
-                title=values[c - 1]['artist'], url='https://e926.net/post/show/{}'.format(keys[c - 1]), color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
-            embed.set_image(url=values[c - 1]['file_url'])
-            embed.set_author(name=formatter.tostring(tags, order=order),
-                             url='https://e926.net/post?tags={}'.format(','.join(tags)), icon_url=ctx.author.avatar_url)
-            embed.set_footer(text=values[c - 1]['score'],
-                             icon_url=self._get_score(values[c - 1]['score']))
-
-            paginator = await dest.send(embed=embed)
-
-            for emoji in ('\N{HEAVY BLACK HEART}', '\N{LEFTWARDS BLACK ARROW}', '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}', '\N{BLACK RIGHTWARDS ARROW}'):
-                await paginator.add_reaction(emoji)
-            await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
-            await asyncio.sleep(1)
-
-            while not self.bot.is_closed():
-                try:
-                    await asyncio.gather(*[self.bot.wait_for('reaction_add', check=on_reaction, timeout=7 * 60),
-                                           self.bot.wait_for('reaction_remove', check=on_reaction, timeout=7 * 60)])
-
-                except exc.Save:
-                    if keys[c - 1] not in hearted:
-                        hearted[keys[c - 1]] = copy.deepcopy(embed)
-
-                        await paginator.edit(content='\N{HEAVY BLACK HEART}')
-                    else:
-                        del hearted[keys[c - 1]]
-
-                        await paginator.edit(content='\N{BROKEN HEART}')
-
-                except exc.Left:
-                    if c > 1:
-                        c -= 1
-                        embed.title = values[c - 1]['artist']
-                        embed.url = 'https://e926.net/post/show/{}'.format(
-                            keys[c - 1])
-                        embed.set_footer(text=values[c - 1]['score'],
-                                         icon_url=self._get_score(values[c - 1]['score']))
-                        embed.set_image(url=values[c - 1]['file_url'])
-
-                        await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
-                    else:
-                        await paginator.edit(content='\N{BLACK RIGHTWARDS ARROW}')
-
-                except exc.GoTo:
-                    await paginator.edit(content=f'`{c} / {len(posts)}`')
-                    number = await self.bot.wait_for('message', check=on_message, timeout=7 * 60)
-
-                    if int(number.content) != 0:
-                        c = int(number.content)
-
-                        embed.title = values[c - 1]['artist']
-                        embed.url = 'https://e926.net/post/show/{}'.format(
-                            keys[c - 1])
-                        embed.set_footer(text=values[c - 1]['score'],
-                                         icon_url=self._get_score(values[c - 1]['score']))
-                        embed.set_image(url=values[c - 1]['file_url'])
-
-                    await number.delete()
-
-                    await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
-
-                except exc.Right:
-                    try:
-                        if c % limit == 0:
-                            await dest.trigger_typing()
-                            temposts, order = await self._get_posts(ctx, booru='e926', tags=tags, limit=limit, previous=posts)
-                            posts.update(temposts)
-
-                            keys = list(posts.keys())
-                            values = list(posts.values())
-
-                        if c < len(keys):
-                            c += 1
-                            embed.title = values[c - 1]['artist']
-                            embed.url = 'https://e926.net/post/show/{}'.format(
-                                keys[c - 1])
-                            embed.set_footer(text=values[c - 1]['score'],
-                                             icon_url=self._get_score(values[c - 1]['score']))
-                            embed.set_image(url=values[c - 1]['file_url'])
-
-                            await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
-                        else:
-                            await paginator.edit(content='\N{LEFTWARDS BLACK ARROW}')
-
-                    except exc.NotFound:
-                        await paginator.edit(content='\N{LEFTWARDS BLACK ARROW}')
-
-        except exc.Abort:
-            try:
-                await paginator.edit(content='\N{WHITE HEAVY CHECK MARK}')
-            except UnboundLocalError:
-                await dest.send('\N{WHITE HEAVY CHECK MARK}')
-        except asyncio.TimeoutError:
-            try:
-                await paginator.edit(content='\N{HOURGLASS}')
-            except UnboundLocalError:
-                await dest.send('\N{HOURGLASS}')
-        except exc.NotFound as e:
-            await ctx.send('`{}` **not found**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.TagBlacklisted as e:
-            await ctx.send('\N{NO ENTRY SIGN} `{}` **blacklisted**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{NO ENTRY SIGN}')
-        except exc.TagBoundsError as e:
-            await ctx.send('`{}` **out of bounds.** Tags limited to 5.'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.FavoritesNotFound:
-            await ctx.send('**You have no favorite tags**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.Timeout:
-            await ctx.send('**Request timed out**')
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-        finally:
-            if hearted:
-                await ctx.message.add_reaction('\N{HOURGLASS WITH FLOWING SAND}')
-
-                n = 1
-                for embed in hearted.values():
-                    await asyncio.sleep(self.RATE_LIMIT)
-
-                    await ctx.author.send(content=f'`{n} / {len(hearted)}`', embed=embed)
-                    n += 1
-
-    # Searches for and returns images from e621.net given tags when not blacklisted
-    @cmds.command(aliases=['e6', '6'], brief='e621 | NSFW', description='e621 | NSFW\nTag-based search for e621.net\n\nYou can only search 5 tags and 6 images at once for now.\ne6 [tags...] ([# of images])')
-    @checks.is_nsfw()
-    async def e621(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args, limit=3)
-            dest, args, limit = kwargs['destination'], kwargs['remaining'], kwargs['limit']
-
-            tags = self._get_favorites(ctx, args)
-
-            await dest.trigger_typing()
-
-            posts, order = await self._get_posts(ctx, booru='e621', tags=tags, limit=limit)
-
-            for ident, post in posts.items():
-                embed = d.Embed(title=post['artist'], url='https://e621.net/post/show/{}'.format(ident),
-                                color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
-                embed.set_image(url=post['file_url'])
-                embed.set_author(name=formatter.tostring(tags, order=order),
-                                 url='https://e621.net/post?tags={}'.format(','.join(tags)), icon_url=ctx.author.avatar_url)
-                embed.set_footer(
-                    text=post['score'], icon_url=self._get_score(post['score']))
-
-                message = await dest.send(embed=embed)
-
-                self.bot.loop.create_task(self.queue_for_hearts(message=message, send=embed))
-
-        except exc.TagBlacklisted as e:
-            await ctx.send('`{}` **blacklisted**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.BoundsError as e:
-            await ctx.send('`{}` **out of bounds.** Images limited to 3.'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.TagBoundsError as e:
-            await ctx.send('`{}` **out of bounds.** Tags limited to 5.'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.NotFound as e:
-            await ctx.send('`{}` **not found**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.FavoritesNotFound:
-            await ctx.send('**You have no favorite tags**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.Timeout:
-            await ctx.send('**Request timed out**')
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    # @e621.error
-    # async def e621_error(self, ctx, error):
-    #     if isinstance(error, exc.NSFW):
-    #         await ctx.send('\N{NO ENTRY} {} **is not an NSFW channel**'.format(ctx.channel.mention), delete_after=7)
-    #         await ctx.message.add_reaction('\N{NO ENTRY}')
-
-    # Searches for and returns images from e926.net given tags when not blacklisted
-    @cmds.command(aliases=['e9', '9'], brief='e926 | SFW', description='e926 | SFW\nTag-based search for e926.net\n\nYou can only search 5 tags and 6 images at once for now.\ne9 [tags...] ([# of images])')
-    async def e926(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args, limit=3)
-            dest, args, limit = kwargs['destination'], kwargs['remaining'], kwargs['limit']
-
-            tags = self._get_favorites(ctx, args)
-
-            await dest.trigger_typing()
-
-            posts, order = await self._get_posts(ctx, booru='e926', tags=tags, limit=limit)
-
-            for ident, post in posts.items():
-                embed = d.Embed(title=post['artist'], url='https://e926.net/post/show/{}'.format(ident),
-                                color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
-                embed.set_image(url=post['file_url'])
-                embed.set_author(name=formatter.tostring(tags, order=order),
-                                 url='https://e621.net/post?tags={}'.format(','.join(tags)), icon_url=ctx.author.avatar_url)
-                embed.set_footer(
-                    text=post['score'], icon_url=self._get_score(post['score']))
-
-                message = await dest.send(embed=embed)
-
-                self.bot.loop.create_task(self.queue_for_hearts(message=message, send=embed))
-
-        except exc.TagBlacklisted as e:
-            await ctx.send('`{}` **blacklisted**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.BoundsError as e:
-            await ctx.send('`{}` **out of bounds.** Images limited to 3.'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.TagBoundsError as e:
-            await ctx.send('`{}` **out of bounds.** Tags limited to 5.'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.NotFound as e:
-            await ctx.send('`{}` **not found**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.FavoritesNotFound:
-            await ctx.send('**You have no favorite tags**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.Timeout:
-            await ctx.send('**Request timed out**')
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @cmds.group(aliases=['fave', 'fav', 'f'])
-    async def favorite(self, ctx):
-        if not ctx.invoked_subcommand:
-            await ctx.send('**Use a flag to manage favorites.**\n*Type* `{}help fav` *for more info.*'.format(ctx.prefix), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @favorite.error
-    async def favorite_error(self, ctx, error):
-        pass
-
-    @favorite.group(name='get', aliases=['g'])
-    async def _get_favorite(self, ctx):
-        pass
-
-    @_get_favorite.command(name='tags', aliases=['t'])
-    async def __get_favorite_tags(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        await dest.send('\N{WHITE MEDIUM STAR} {}**\'s favorite tags:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(self.favorites.get(ctx.author.id, {}).get('tags', set()))), delete_after=7)
-
-    @_get_favorite.command(name='posts', aliases=['p'])
-    async def __get_favorite_posts(self, ctx):
-        pass
-
-    @favorite.group(name='add', aliases=['a'])
-    async def _add_favorite(self, ctx):
-        pass
-
-    @_add_favorite.command(name='tags', aliases=['t'])
-    async def __add_favorite_tags(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, tags = kwargs['destination'], kwargs['remaining']
-
-            for tag in tags:
-                if tag in self.blacklists['user_blacklist'].get(ctx.author.id, set()):
-                    raise exc.TagBlacklisted(tag)
-            with suppress(KeyError):
-                if len(self.favorites[ctx.author.id]['tags']) + len(tags) > 5:
-                    raise exc.BoundsError
-
-            self.favorites.setdefault(ctx.author.id, {}).setdefault(
-                'tags', set()).update(tags)
-            u.dump(self.favorites, 'cogs/favorites.pkl')
-
-            await dest.send('{} **added to their favorites:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(tags)), delete_after=5)
-
-        except exc.BoundsError:
-            await ctx.send('**Favorites list currently limited to:** `5`', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.TagBlacklisted as e:
-            await ctx.send('\N{NO ENTRY SIGN} `{}` **blacklisted**', delete_after=7)
-            await ctx.message.add_reaction('\N{NO ENTRY SIGN}')
-
-    @_add_favorite.command(name='posts', aliases=['p'])
-    async def __add_favorite_posts(self, ctx, *posts):
-        pass
-
-    @favorite.group(name='remove', aliases=['r'])
-    async def _remove_favorite(self, ctx):
-        pass
-
-    @_remove_favorite.command(name='tags', aliases=['t'])
-    async def __remove_favorite_tags(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, tags = kwargs['destination'], kwargs['remaining']
-
-            for tag in tags:
-                try:
-                    self.favorites[ctx.author.id].get(
-                        'tags', set()).remove(tag)
-
-                except KeyError:
-                    raise exc.TagError(tag)
-
-            u.dump(self.favorites, 'cogs/favorites.pkl')
-
-            await dest.send('{} **removed from their favorites:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(tags)), delete_after=5)
-
-        except KeyError:
-            await ctx.send('**You do not have any favorites**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except exc.TagError as e:
-            await ctx.send('`{}` **not in favorites**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @_remove_favorite.command(name='posts', aliases=['p'])
-    async def __remove_favorite_posts(self, ctx):
-        pass
-
-    @favorite.group(name='clear', aliases=['c'])
-    async def _clear_favorite(self, ctx):
-        pass
-
-    @_clear_favorite.command(name='tags', aliases=['t'])
-    async def __clear_favorite_tags(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        with suppress(KeyError):
-            del self.favorites[ctx.author.id]
-            u.dump(self.favorites, 'cogs/favorites.pkl')
-
-        await dest.send('{}**\'s favorites cleared**'.format(ctx.author.mention), delete_after=5)
-
-    @_clear_favorite.command(name='posts', aliases=['p'])
-    async def __clear_favorite_posts(self, ctx):
-        pass
-
-    # Umbrella command structure to manage global, channel, and user blacklists
-    @cmds.group(aliases=['bl', 'b'], brief='(G) Manage blacklists', description='Manage channel or personal blacklists\n\nUsage:\n\{p\}bl get \{blacklist\} to show a blacklist\n\{p\}bl clear \{blacklist\} to clear a blacklist\n\{p\}bl add \{blacklist\} \{tags...\} to add tag(s) to a blacklist\n\{p\}bl remove \{blacklist\} \{tags...\} to remove tags from a blacklist')
-    async def blacklist(self, ctx):
-        if not ctx.invoked_subcommand:
-            await ctx.send('**Use a flag to manage blacklists.**\n*Type* `{}help bl` *for more info.*'.format(ctx.prefix), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    # @blacklist.error
-    # async def blacklist_error(self, ctx, error):
-        # if isinstance(error, KeyError):
-        #     return await ctx.send('**Blacklist does not exist**', delete_after=7)
-
-    @blacklist.group(name='get', aliases=['g'], brief='(G) Get a blacklist\n\nUsage:\n\{p\}bl get \{blacklist\}')
-    async def _get_blacklist(self, ctx):
-        if not ctx.invoked_subcommand:
-            await ctx.send('**Invalid blacklist**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @_get_blacklist.command(name='global', aliases=['gl', 'g'], brief='Get current global blacklist', description='Get current global blacklist\n\nThis applies to all booru commands, in accordance with Discord\'s ToS agreement\n\nExample:\n\{p\}bl get global')
-    async def __get_global_blacklist(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        await dest.send('\N{NO ENTRY SIGN} **Global blacklist:**\n```\n{}```'.format(formatter.tostring(self.blacklists['global_blacklist'])))
-
-    @_get_blacklist.command(name='channel', aliases=['ch', 'c'], brief='Get current channel blacklist', description='Get current channel blacklist\n\nThis is based on context - the channel where the command was executed\n\nExample:\{p\}bl get channel')
-    async def __get_channel_blacklist(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        guild = ctx.guild if isinstance(
-            ctx.guild, d.Guild) else ctx.channel
-
-        await dest.send('\N{NO ENTRY SIGN} {} **blacklist:**\n```\n{}```'.format(ctx.channel.mention, formatter.tostring(self.blacklists['guild_blacklist'].get(guild.id, {}).get(ctx.channel.id, set()))))
-
-    @_get_blacklist.command(name='me', aliases=['m'], brief='Get your personal blacklist', description='Get your personal blacklist\n\nYour blacklist is not viewable by anyone but you, except if you call this command in a public channel. The blacklist will be deleted soon after for your privacy\n\nExample:\n\{p\}bl get me')
-    async def __get_user_blacklist(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        await dest.send('\N{NO ENTRY SIGN} {}**\'s blacklist:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(self.blacklists['user_blacklist'].get(ctx.author.id, set()))), delete_after=7)
-
-    @_get_blacklist.command(name='here', aliases=['h'], brief='Get current global and channel blacklists', description='Get current global and channel blacklists in a single message\n\nExample:\{p\}bl get here')
-    async def __get_here_blacklists(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        guild = ctx.guild if isinstance(
-            ctx.guild, d.Guild) else ctx.channel
-
-        await dest.send('\N{NO ENTRY SIGN} **__Blacklisted:__**\n\n**Global:**\n```\n{}```\n**{}:**\n```\n{}```'.format(formatter.tostring(self.blacklists['global_blacklist']), ctx.channel.mention, formatter.tostring(self.blacklists['guild_blacklist'].get(guild.id, {}).get(ctx.channel.id, set()))))
-
-    @_get_blacklist.group(name='all', aliases=['a'], hidden=True)
-    async def __get_all_blacklists(self, ctx):
-        if not ctx.invoked_subcommand:
-            await ctx.send('**Invalid blacklist**')
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @__get_all_blacklists.command(name='guild', aliases=['g'])
-    @cmds.has_permissions(manage_channels=True)
-    async def ___get_all_guild_blacklists(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        guild = ctx.guild if isinstance(
-            ctx.guild, d.Guild) else ctx.channel
-
-        await dest.send('\N{NO ENTRY SIGN} **__{} blacklists:__**\n\n{}'.format(guild.name, formatter.dict_tostring(self.blacklists['guild_blacklist'].get(guild.id, {}))))
-
-    @__get_all_blacklists.command(name='user', aliases=['u', 'member', 'm'])
-    @cmds.is_owner()
-    async def ___get_all_user_blacklists(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        await dest.send('\N{NO ENTRY SIGN} **__User blacklists:__**\n\n{}'.format(formatter.dict_tostring(self.blacklists['user_blacklist'])))
-
-    @blacklist.group(name='add', aliases=['a'], brief='(G) Add tag(s) to a blacklist\n\nUsage:\n\{p\}bl add \{blacklist\} \{tags...\}')
-    async def _add_tags(self, ctx):
-        if not ctx.invoked_subcommand:
-            await ctx.send('**Invalid blacklist**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @_add_tags.command(name='global', aliases=['gl', 'g'])
-    @cmds.is_owner()
-    async def __add_global_tags(self, ctx, *args):
-        kwargs = u.get_kwargs(ctx, args)
-        dest, tags = kwargs['destination'], kwargs['remaining']
-
-        await dest.trigger_typing()
-
-        self.blacklists['global_blacklist'].update(tags)
-        for tag in tags:
-            alias_request = await u.fetch('https://e621.net/tag_alias/index.json', params={'aliased_to': tag, 'approved': 'true'}, json=True)
-            if alias_request:
-                for dic in alias_request:
-                    self.aliases.setdefault(tag, set()).add(dic['name'])
-            else:
-                self.aliases.setdefault(tag, set())
-        u.dump(self.blacklists, 'cogs/blacklists.pkl')
-        u.dump(self.aliases, 'cogs/aliases.pkl')
-
-        await dest.send('**Added to global blacklist:**\n```\n{}```'.format(formatter.tostring(tags)), delete_after=5)
-
-    @_add_tags.command(name='channel', aliases=['ch', 'c'], brief='@manage_channel@ Add tag(s) to the current channel blacklist (requires manage_channel)', description='Add tag(s) to the current channel blacklist ')
-    @cmds.has_permissions(manage_channels=True)
-    async def __add_channel_tags(self, ctx, *args):
-        kwargs = u.get_kwargs(ctx, args)
-        dest, tags = kwargs['destination'], kwargs['remaining']
-
-        guild = ctx.guild if isinstance(
-            ctx.guild, d.Guild) else ctx.channel
-
-        await dest.trigger_typing()
-
-        self.blacklists['guild_blacklist'].setdefault(
-            guild.id, {}).setdefault(ctx.channel.id, set()).update(tags)
-        for tag in tags:
-            alias_request = await u.fetch('https://e621.net/tag_alias/index.json', params={'aliased_to': tag, 'approved': 'true'}, json=True)
-            if alias_request:
-                for dic in alias_request:
-                    self.aliases.setdefault(tag, set()).add(dic['name'])
-            else:
-                self.aliases.setdefault(tag, set())
-        u.dump(self.blacklists, 'cogs/blacklists.pkl')
-        u.dump(self.aliases, 'cogs/aliases.pkl')
-
-        await dest.send('**Added to** {} **blacklist:**\n```\n{}```'.format(ctx.channel.mention, formatter.tostring(tags)), delete_after=5)
-
-    @_add_tags.command(name='me', aliases=['m'])
-    async def __add_user_tags(self, ctx, *args):
-        kwargs = u.get_kwargs(ctx, args)
-        dest, tags = kwargs['destination'], kwargs['remaining']
-
-        await dest.trigger_typing()
-
-        self.blacklists['user_blacklist'].setdefault(
-            ctx.author.id, set()).update(tags)
-        for tag in tags:
-            alias_request = await u.fetch('https://e621.net/tag_alias/index.json', params={'aliased_to': tag, 'approved': 'true'}, json=True)
-            if alias_request:
-                for dic in alias_request:
-                    self.aliases.setdefault(tag, set()).add(dic['name'])
-            else:
-                self.aliases.setdefault(tag, set())
-        u.dump(self.blacklists, 'cogs/blacklists.pkl')
-        u.dump(self.aliases, 'cogs/aliases.pkl')
-
-        await dest.send('{} **added to their blacklist:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(tags)), delete_after=5)
-
-    @blacklist.group(name='remove', aliases=['rm', 'r'])
-    async def _remove_tags(self, ctx):
-        if not ctx.invoked_subcommand:
-            await ctx.send('**Invalid blacklist**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @_remove_tags.command(name='global', aliases=['gl', 'g'])
-    @cmds.is_owner()
-    async def __remove_global_tags(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, tags = kwargs['destination'], kwargs['remaining']
-
-            for tag in tags:
-                try:
-                    self.blacklists['global_blacklist'].remove(tag)
-
-                except KeyError:
-                    raise exc.TagError(tag)
-
-            u.dump(self.blacklists, 'cogs/blacklists.pkl')
-
-            await dest.send('**Removed from global blacklist:**\n```\n{}```'.format(formatter.tostring(tags)), delete_after=5)
-
-        except exc.TagError as e:
-            await ctx.send('`{}` **not in blacklist**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @_remove_tags.command(name='channel', aliases=['ch', 'c'])
-    @cmds.has_permissions(manage_channels=True)
-    async def __remove_channel_tags(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, tags = kwargs['destination'], kwargs['remaining']
-
-            guild = ctx.guild if isinstance(
-                ctx.guild, d.Guild) else ctx.channel
-
-            for tag in tags:
-                try:
-                    self.blacklists['guild_blacklist'][guild.id][ctx.channel.id].remove(
-                        tag)
-
-                except KeyError:
-                    raise exc.TagError(tag)
-
-            u.dump(self.blacklists, 'cogs/blacklists.pkl')
-
-            await dest.send('**Removed from** {} **blacklist:**\n```\n{}```'.format(ctx.channel.mention, formatter.tostring(tags), delete_after=5))
-
-        except exc.TagError as e:
-            await ctx.send('`{}` **not in blacklist**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @_remove_tags.command(name='me', aliases=['m'])
-    async def __remove_user_tags(self, ctx, *args):
-        try:
-            kwargs = u.get_kwargs(ctx, args)
-            dest, tags = kwargs['destination'], kwargs['remaining']
-
-            for tag in tags:
-                try:
-                    self.blacklists['user_blacklist'][ctx.author.id].remove(
-                        tag)
-
-                except KeyError:
-                    raise exc.TagError(tag)
-
-            u.dump(self.blacklists, 'cogs/blacklists.pkl')
-
-            await dest.send('{} **removed from their blacklist:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(tags)), delete_after=5)
-
-        except exc.TagError as e:
-            await ctx.send('`{}` **not in blacklist**'.format(e), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @blacklist.group(name='clear', aliases=['cl', 'c'])
-    async def _clear_blacklist(self, ctx):
-        if not ctx.invoked_subcommand:
-            await ctx.send('**Invalid blacklist**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @_clear_blacklist.command(name='global', aliases=['gl', 'g'])
-    @cmds.is_owner()
-    async def __clear_global_blacklist(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        self.blacklists['global_blacklist'].clear()
-        u.dump(self.blacklists, 'cogs/blacklists.pkl')
-
-        await dest.send('**Global blacklist cleared**', delete_after=5)
-
-    @_clear_blacklist.command(name='channel', aliases=['ch', 'c'])
-    @cmds.has_permissions(manage_channels=True)
-    async def __clear_channel_blacklist(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        guild = ctx.guild if isinstance(
-            ctx.guild, d.Guild) else ctx.channel
-
-        with suppress(KeyError):
-            del self.blacklists['guild_blacklist'][guild.id][ctx.channel.id]
-            u.dump(self.blacklists, 'cogs/blacklists.pkl')
-
-        await dest.send('{} **blacklist cleared**'.format(ctx.channel.mention), delete_after=5)
-
-    @_clear_blacklist.command(name='me', aliases=['m'])
-    async def __clear_user_blacklist(self, ctx, *args):
-        dest = u.get_kwargs(ctx, args)['destination']
-
-        with suppress(KeyError):
-            del self.blacklists['user_blacklist'][ctx.author.id]
-            u.dump(self.blacklists, 'cogs/blacklists.pkl')
-
-        await dest.send('{}**\'s blacklist cleared**'.format(ctx.author.mention), delete_after=5)
+import asyncio
+import json
+import re
+import sys
+import traceback as tb
+from contextlib import suppress
+from datetime import datetime as dt
+from datetime import timedelta as td
+from fractions import gcd
+import copy
+
+import discord as d
+from discord import errors as err
+from discord import reaction
+from discord.ext import commands as cmds
+from discord.ext.commands import errors as errext
+
+from cogs import tools
+from misc import exceptions as exc
+from misc import checks
+from utils import utils as u
+from utils import formatter, scraper
+
+
+class MsG:
+
+    def __init__(self, bot):
+        self.bot = bot
+        self.LIMIT = 100
+        self.HISTORY_LIMIT = 150
+        self.RATE_LIMIT = u.RATE_LIMIT
+        self.reversiqueue = asyncio.Queue()
+        self.heartqueue = asyncio.Queue()
+        self.reversifying = False
+        self.updating = False
+        self.hearting = False
+
+        time = (dt.utcnow() - td(days=29)).strftime('%d/%m/%Y/%H:%M:%S')
+        self.suggested = u.setdefault('cogs/suggested.pkl', 7)
+        # self.suggested = u.setdefault('cogs/suggested.pkl', {'last_update': 'test', 'tags': {}, 'total': 1})
+        print(self.suggested)
+        self.favorites = u.setdefault('cogs/favorites.pkl', {})
+        self.blacklists = u.setdefault(
+            'cogs/blacklists.pkl', {'global_blacklist': set(), 'guild_blacklist': {}, 'user_blacklist': {}})
+        self.aliases = u.setdefault('cogs/aliases.pkl', {})
+
+        if not self.hearting:
+            self.hearting = True
+            self.bot.loop.create_task(self._send_hearts())
+            print('STARTED : hearting')
+        if u.tasks['auto_rev']:
+            for channel in u.tasks['auto_rev']:
+                temp = self.bot.get_channel(channel)
+                self.bot.loop.create_task(self.queue_for_reversification(temp))
+                print('STARTED : auto-reversifying in #{}'.format(temp.name))
+            self.reversifying = True
+            self.bot.loop.create_task(self._reversify())
+        if u.tasks['auto_hrt']:
+            for channel in u.tasks['auto_hrt']:
+                temp = self.bot.get_channel(channel)
+                self.bot.loop.create_task(self.queue_for_hearts(channel=temp))
+                print(f'STARTED : auto-hearting in #{temp.name}')
+        # if not self.updating:
+        #     self.updating = True
+        #     self.bot.loop.create_task(self._update_suggested())
+
+    async def _update_suggested(self):
+        while self.updating:
+            print('Checking for tag updates...')
+            print(self.suggested)
+
+            time = dt.utcnow()
+            last_update = dt.strptime(self.suggested['last_update'], '%d/%m/%Y/%H:%M:%S')
+            delta = time - last_update
+            print(delta.days)
+
+            if delta.days < 30:
+                print('Up to date.')
+            else:
+                page = 1
+                pages = len(list(self.suggested['tags'].keys()))
+
+                print(f'Last updated: {self.suggested["last_update"]}')
+                print('Updating tags...')
+
+                content = await u.fetch('https://e621.net/tag/index.json', params={'order': 'count', 'limit': 500, 'page': page}, json=True)
+                while content:
+                    for tag in content:
+                        self.suggested['tags'][tag['name']] = tag['count']
+                        self.suggested['total'] += tag['count']
+                    print(f'    UPDATED : PAGE {page} / {pages}', end='\r')
+
+                    page += 1
+                    content = await u.fetch('https://e621.net/tag/index.json', params={'order': 'count', 'limit': 500, 'page': page}, json=True)
+
+                u.dump(self.suggested, 'cogs/suggested.pkl')
+                self.suggested['last_update'] = time.strftime('%d/%m/%Y/%H:%M:%S')
+
+                print('\nFinished updating tags.')
+
+            await asyncio.sleep(24 * 60 * 60)
+
+    def _get_favorites(self, ctx, args):
+        if '-f' in args or '-favs' in args or '-faves' in args or '-favorites' in args:
+            if self.favorites.get(ctx.author.id, {}).get('tags', set()):
+                args = ['~{}'.format(tag)
+                        for tag in self.favorites[ctx.author.id]['tags']]
+            else:
+                raise exc.FavoritesNotFound
+
+        return args
+
+    def _get_score(self, score):
+        if score < 0:
+            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/pouting-face_1f621.png'
+        elif score == 0:
+            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/mozilla/36/pile-of-poo_1f4a9.png'
+        elif 10 > score > 0:
+            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/white-medium-star_2b50.png'
+        elif 50 > score >= 10:
+            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/glowing-star_1f31f.png'
+        elif 100 > score >= 50:
+            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/dizzy-symbol_1f4ab.png'
+        elif score >= 100:
+            return 'https://emojipedia-us.s3.amazonaws.com/thumbs/320/twitter/103/sparkles_2728.png'
+        return None
+
+    async def _send_hearts(self):
+        while self.hearting:
+            temp = await self.heartqueue.get()
+
+            if isinstance(temp[1], d.Embed):
+                await temp[0].send(embed=temp[1])
+
+                await asyncio.sleep(self.RATE_LIMIT)
+            elif isinstance(temp[1], d.Message):
+                for match in re.finditer('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', temp[1].content):
+                    await temp[0].send(match)
+
+                    await asyncio.sleep(self.RATE_LIMIT)
+
+                for attachment in temp[1].attachments:
+                    await temp[0].send(attachment.url)
+                    await asyncio.sleep(self.RATE_LIMIT)
+
+        print('STOPPED : hearting')
+
+    async def queue_for_hearts(self, *, message=None, send=None, channel=None, reaction=True, timeout=60 * 60):
+        def on_reaction(reaction, user):
+            if reaction.emoji == '\N{HEAVY BLACK HEART}' and reaction.message.id == message.id and not user.bot:
+                raise exc.Save(user)
+            return False
+        def on_reaction_channel(reaction, user):
+            if reaction.message.channel.id == channel.id and not user.bot:
+                if reaction.emoji == '\N{OCTAGONAL SIGN}' and user.permissions_in(reaction.message.channel).administrator:
+                    raise exc.Abort
+                if reaction.emoji == '\N{HEAVY BLACK HEART}' and (re.search('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', reaction.message.content) or reaction.message.attachments):
+                    raise exc.Save(user, reaction.message)
+            return False
+
+        if message:
+            try:
+                if reaction:
+                    await message.add_reaction('\N{HEAVY BLACK HEART}')
+                    await asyncio.sleep(1)
+
+                while self.hearting:
+                    try:
+                        await self.bot.wait_for('reaction_add', check=on_reaction, timeout=timeout)
+
+                    except exc.Save as e:
+                        await self.heartqueue.put((e.user, send if send else message))
+
+            except asyncio.TimeoutError:
+                await message.add_reaction('\N{WHITE HEAVY CHECK MARK}')
+        else:
+            try:
+                while self.hearting:
+                    try:
+                        await self.bot.wait_for('reaction_add', check=on_reaction_channel)
+
+                    except exc.Save as e:
+                        await self.heartqueue.put((e.user, message))
+
+            except exc.Abort:
+                u.tasks['auto_hrt'].remove(channel.id)
+                u.dump(u.tasks, 'cogs/tasks.pkl')
+                print('STOPPED : auto-hearting in #{}'.format(channel.name))
+                await channel.send('**Stopped queueing messages for hearting in** {}'.format(channel.mention), delete_after=5)
+
+    @cmds.command(name='autoheart', aliases=['autohrt'])
+    @cmds.has_permissions(administrator=True)
+    async def auto_heart(self, ctx):
+        try:
+            if ctx.channel.id not in u.tasks['auto_hrt']:
+                u.tasks['auto_hrt'].append(ctx.channel.id)
+                u.dump(u.tasks, 'cogs/tasks.pkl')
+                self.bot.loop.create_task(self.queue_for_hearts(channel=ctx.channel))
+                print('STARTED : auto-hearting in #{}'.format(ctx.channel.name))
+                await ctx.send('**Auto-hearting all messages in {}**'.format(ctx.channel.mention), delete_after=5)
+            else:
+                raise exc.Exists
+
+        except exc.Exists:
+            message = await ctx.send('**Already auto-hearting in {}.** React with \N{OCTAGONAL SIGN} to stop.'.format(ctx.channel.mention))
+            await message.add_reaction('\N{OCTAGONAL SIGN}')
+
+    # @cmds.command()
+    # async def auto_post(self, ctx):
+    #     try:
+    #         if ctx.channel.id not in u.tasks['auto_post']:
+    #             u.tasks['auto_post'].append(ctx.channel.id)
+    #             u.dump(u.tasks, 'cogs/tasks.pkl')
+    #             self.bot.loop.create_task(self.queue_for_posting(ctx.channel))
+    #             if not self.posting:
+    #                 self.bot.loop.create_task(self._post())
+    #                 self.posting = True
+    #
+    #             print('STARTED : auto-posting in #{}'.format(ctx.channel.name))
+    #             await ctx.send('**Auto-posting all images in {}**'.format(ctx.channel.mention), delete_after=5)
+    #         else:
+    #             raise exc.Exists
+    #
+    #     except exc.Exists:
+    #         await ctx.send('**Already auto-posting in {}.** Type `stop` to stop.'.format(ctx.channel.mention), delete_after=7)
+    #         await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @cmds.group(aliases=['tag', 't'], brief='(G) Get info on tags', description='Group command for obtaining info on tags\n\nUsage:\n\{p\}tag \{flag\} \{tag(s)\}')
+    async def tags(self, ctx):
+        pass
+
+    # Tag search
+    @tags.command(name='related', aliases=['relate', 'rel', 'r'], brief='(tags) Search for related tags', description='Return related tags for given tag(s)\n\nExample:\n\{p\}tag related wolf')
+    async def _tags_related(self, ctx, *args):
+        kwargs = u.get_kwargs(ctx, args)
+        dest, tags = kwargs['destination'], kwargs['remaining']
+        related = []
+        c = 0
+
+        await dest.trigger_typing()
+
+        for tag in tags:
+            try:
+                tag_request = await u.fetch('https://e621.net/tag/related.json', params={'tags': tag}, json=True)
+                for rel in tag_request.get(tag, []):
+                    related.append(rel[0])
+
+                if related:
+                    await dest.send('`{}` **related tags:**\n```\n{}```'.format(tag, formatter.tostring(related)))
+                else:
+                    await ctx.send(f'**No related tags found for:** `{tag}`', delete_after=7)
+
+                related.clear()
+                c += 1
+
+            finally:
+                await asyncio.sleep(self.RATE_LIMIT)
+
+        if not c:
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    # Tag aliases
+    @tags.command(name='aliases', aliases=['alias', 'als', 'a'], brief='(tags) Search for tag aliases', description='Return aliases for given tag(s)\n\nExample:\n\{p\}tag alias wolf')
+    async def _tags_aliases(self, ctx, *args):
+        kwargs = u.get_kwargs(ctx, args)
+        dest, tags = kwargs['destination'], kwargs['remaining']
+        aliases = []
+        c = 0
+
+        await dest.trigger_typing()
+
+        for tag in tags:
+            try:
+                alias_request = await u.fetch('https://e621.net/tag_alias/index.json', params={'aliased_to': tag, 'approved': 'true'}, json=True)
+                for dic in alias_request:
+                    aliases.append(dic['name'])
+
+                if aliases:
+                    await dest.send('`{}` **aliases:**\n```\n{}```'.format(tag, formatter.tostring(aliases)))
+                else:
+                    await ctx.send(f'**No aliases found for:** `{tag}`', delete_after=7)
+
+                aliases.clear()
+                c += 1
+
+            finally:
+                await asyncio.sleep(self.RATE_LIMIT)
+
+        if not c:
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @cmds.group(aliases=['g'], brief='(G) Get e621 elements', description='Group command for obtaining various elements like post info\n\nUsage:\n\{p\}get \{flag\} \{args\}')
+    async def get(self, ctx):
+        if not ctx.invoked_subcommand:
+            await ctx.send('**Use a flag to get items.**\n*Type* `{}help get` *for more info.*'.format(ctx.prefix), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @get.command(name='info', aliases=['i'], brief='(get) Get info from post', description='Return info for given post URL or ID\n\nExample:\n\{p\}get info 1145042')
+    async def _get_info(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, posts = kwargs['destination'], kwargs['remaining']
+
+            if not posts:
+                raise exc.MissingArgument
+
+            for ident in posts:
+                try:
+                    await dest.trigger_typing()
+
+                    ident = ident if not ident.isdigit() else re.search(
+                        'show/([0-9]+)', ident).group(1)
+                    post = await u.fetch('https://e621.net/post/show.json', params={'id': ident}, json=True)
+
+                    embed = d.Embed(
+                        title=', '.join(post['artist']), url=f'https://e621.net/post/show/{post["id"]}', color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
+                    embed.set_thumbnail(url=post['file_url'])
+                    embed.set_author(name=f'{post["width"]} x {post["height"]}',
+                                     url=f'https://e621.net/post?tags=ratio:{post["width"]/post["height"]:.2f}', icon_url=ctx.author.avatar_url)
+                    embed.set_footer(text=post['score'],
+                                     icon_url=self._get_score(post['score']))
+
+                # except
+
+                finally:
+                    await asyncio.sleep(self.RATE_LIMIT)
+
+        except exc.MissingArgument:
+            await ctx.send('**Invalid url**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @get.command(name='image', aliases=['img'], brief='(get) Get direct image from post', description='Return direct image URL for given post\n\nExample:\n\{p\}get image 1145042')
+    async def _get_image(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, urls = kwargs['destination'], kwargs['remaining']
+            c = 0
+
+            if not urls:
+                raise exc.MissingArgument
+
+            for url in urls:
+                try:
+                    await dest.trigger_typing()
+
+                    await dest.send(await scraper.get_image(url))
+
+                    c += 1
+
+                # except
+                    # await ctx.send(f'**No aliases found for:** `{tag}`', delete_after=7)
+
+                finally:
+                    await asyncio.sleep(self.RATE_LIMIT)
+
+            if not c:
+                await ctx.message.add_reaction('\N{CROSS MARK}')
+
+        except exc.MissingArgument:
+            await ctx.send('**Invalid url or file**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @get.command(name='pool', aliases=['p'], brief='(get) Get pool from query', description='Return pool info for given query\n\nExample:\n\{p\}get pool 1145042')
+    async def _get_pool(self, ctx, *args):
+        def on_reaction(reaction, user):
+            if reaction.emoji == '\N{OCTAGONAL SIGN}' and reaction.message.id == ctx.message.id and user is ctx.author:
+                raise exc.Abort(match)
+            return False
+
+        def on_message(msg):
+            return msg.content.isdigit() and int(msg.content) <= len(pools) and int(msg.content) > 0 and msg.author is ctx.author and msg.channel is ctx.channel
+
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, query = kwargs['destination'], kwargs['remaining']
+            ident = None
+
+            await dest.trigger_typing()
+
+            pools = []
+            pool_request = await u.fetch('https://e621.net/pool/index.json', params={'query': ' '.join(query)}, json=True)
+            if len(pool_request) > 1:
+                for pool in pool_request:
+                    pools.append(pool['name'])
+                match = await ctx.send('**Multiple pools found for `{}`.** Type the number of the correct match\n```\n{}```'.format(' '.join(query), '\n'.join(['{} {}'.format(c, elem) for c, elem in enumerate(pools, 1)])))
+
+                await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
+                done, pending = await asyncio.wait([self.bot.wait_for('reaction_add', check=on_reaction, timeout=60),
+                                                    self.bot.wait_for('reaction_remove', check=on_reaction, timeout=60), self.bot.wait_for('message', check=on_message, timeout=60)], return_when=asyncio.FIRST_COMPLETED)
+                for future in done:
+                    selection = future.result()
+
+                await match.delete()
+                tempool = [pool for pool in pool_request if pool['name']
+                           == pools[int(selection.content) - 1]][0]
+                await selection.delete()
+            elif pool_request:
+                tempool = pool_request[0]
+            else:
+                raise exc.NotFound
+
+            await ctx.send(f'**{tempool["name"]}**\nhttps://e621.net/pool/show/{tempool["id"]}')
+
+        except exc.Abort as e:
+            await e.message.edit(content='\N{NO ENTRY SIGN}', delete_after=7)
+
+    # Reverse image searches a linked image using the public iqdb
+    @cmds.command(name='reverse', aliases=['rev', 'ris'], brief='Reverse image search from e621', description='NSFW\nReverse-search an image with given URL')
+    async def reverse(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, urls = kwargs['destination'], kwargs['remaining']
+            c = 0
+
+            if not urls and not ctx.message.attachments:
+                raise exc.MissingArgument
+
+            for attachment in ctx.message.attachments:
+                urls.append(attachment.url)
+
+            for url in urls:
+                try:
+                    await dest.trigger_typing()
+
+                    post = await scraper.get_post(url)
+
+                    embed = d.Embed(
+                        title=', '.join(post['artist']), url=f'https://e621.net/post/show/{post["id"]}', color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
+                    embed.set_image(url=post['file_url'])
+                    embed.set_author(name=f'{post["width"]} x {post["height"]}',
+                                     url=f'https://e621.net/post?tags=ratio:{post["width"]/post["height"]:.2f}', icon_url=ctx.author.avatar_url)
+                    embed.set_footer(text=post['score'],
+                                     icon_url=self._get_score(post['score']))
+
+                    await dest.send('**Probable match**', embed=embed)
+
+                    c += 1
+
+                except exc.MatchError as e:
+                    await ctx.send('**No probable match for:** `{}`'.format(e), delete_after=7)
+
+            if not c:
+                await ctx.message.add_reaction('\N{CROSS MARK}')
+
+        except exc.MissingArgument:
+            await ctx.send('**Invalid url or file.** Be sure the link directs to an image file', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.SizeError as e:
+            await ctx.send(f'`{e}` **too large.** Maximum is 8 MB', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @cmds.command(name='reversify', aliases=['revify', 'risify', 'rify'])
+    async def reversify(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args, limit=self.HISTORY_LIMIT / 5)
+            dest, remove, limit = kwargs['destination'], kwargs['remove'], kwargs['limit']
+            links = {}
+            c = 0
+
+            if not ctx.author.permissions_in(ctx.channel).manage_messages:
+                dest = ctx.author
+
+            async for message in ctx.channel.history(limit=self.HISTORY_LIMIT * limit):
+                if c >= limit:
+                    break
+                if message.author.id != self.bot.user.id and (re.search('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', message.content) is not None or message.embeds or message.attachments):
+                    links[message] = []
+                    for match in re.finditer('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', message.content):
+                        links[message].append(match.group(0))
+                    for embed in message.embeds:
+                        if embed.image.url is not d.Embed.Empty:
+                            links[message].append(embed.image.url)
+                    for attachment in message.attachments:
+                        links[message].append(attachment.url)
+
+                    await message.add_reaction('\N{HOURGLASS WITH FLOWING SAND}')
+                    c += 1
+
+            if not links:
+                raise exc.NotFound
+
+            n = 1
+            for message, urls in links.items():
+                for url in urls:
+                    try:
+                        await dest.trigger_typing()
+
+                        post = await scraper.get_post(url)
+
+                        embed = d.Embed(
+                            title=', '.join(post['artist']), url=f'https://e621.net/post/show/{post["id"]}', color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
+                        embed.set_image(url=post['file_url'])
+                        embed.set_author(name=f'{post["width"]} x {post["height"]}',
+                                         url=f'https://e621.net/post?tags=ratio:{post["width"]/post["height"]:.2f}', icon_url=ctx.author.avatar_url)
+                        embed.set_footer(
+                            text=post['score'], icon_url=self._get_score(post['score']))
+
+                        await dest.send(f'**Probable match from** {message.author.display_name}', embed=embed)
+                        await message.add_reaction('\N{WHITE HEAVY CHECK MARK}')
+
+                        if remove:
+                            with suppress(err.NotFound):
+                                await message.delete()
+
+                    except exc.MatchError as e:
+                        await ctx.send('`{} / {}` **No probable match for:** `{}`'.format(n, len(links), e), delete_after=7)
+                        await message.add_reaction('\N{CROSS MARK}')
+                        c -= 1
+                    except exc.SizeError as e:
+                        await ctx.send(f'`{e}` **too large.** Maximum is 8 MB', delete_after=7)
+                        await message.add_reaction('\N{CROSS MARK}')
+                        c -= 1
+
+                    finally:
+                        n += 1
+
+            if c <= 0:
+                await ctx.message.add_reaction('\N{CROSS MARK}')
+
+        except exc.NotFound:
+            await ctx.send('**No matches found**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.BoundsError as e:
+            await ctx.send('`{}` **invalid limit.** Query limited to 30'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    async def _reversify(self):
+        while self.reversifying:
+            message = await self.reversiqueue.get()
+            urls = []
+
+            for match in re.finditer('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', message.content):
+                urls.append(match.group(0))
+            for embed in message.embeds:
+                if embed.image.url is not d.Embed.Empty:
+                    urls.append(embed.image.url)
+            for attachment in message.attachments:
+                urls.append(attachment.url)
+
+            for url in urls:
+                try:
+                    await message.channel.trigger_typing()
+
+                    post = await scraper.get_post(url)
+
+                    embed = d.Embed(
+                        title=', '.join(post['artist']), url=f'https://e621.net/post/show/{post["id"]}', color=message.channel.guild.me.color if isinstance(message.channel, d.TextChannel) else u.color)
+                    embed.set_image(url=post['file_url'])
+                    embed.set_author(name=f'{post["width"]} x {post["height"]}',
+                                     url=f'https://e621.net/post?tags=ratio:{post["width"]/post["height"]:.2f}', icon_url=message.author.avatar_url)
+                    embed.set_footer(text=post['score'],
+                                     icon_url=self._get_score(post['score']))
+
+                    await message.channel.send('**Probable match from** {}'.format(message.author.display_name), embed=embed)
+
+                    await message.add_reaction('\N{WHITE HEAVY CHECK MARK}')
+
+                    await asyncio.sleep(self.RATE_LIMIT)
+
+                    with suppress(err.NotFound):
+                        await message.delete()
+
+                except exc.MatchError as e:
+                    await message.channel.send('**No probable match for:** `{}`'.format(e), delete_after=7)
+                    await message.add_reaction('\N{CROSS MARK}')
+                except exc.SizeError as e:
+                    await message.channel.send(f'`{e}` **too large.** Maximum is 8 MB', delete_after=7)
+                    await message.add_reaction('\N{CROSS MARK}')
+                except Exception:
+                    await message.channel.send(f'**An unknown error occurred.**', delete_after=7)
+                    await message.add_reaction('\N{WARNING SIGN}')
+
+        print('STOPPED : reversifying')
+
+    async def queue_for_reversification(self, channel):
+        def check(msg):
+            if 'stop r' in msg.content.lower() and msg.channel is channel and msg.author.guild_permissions.administrator:
+                raise exc.Abort
+            elif msg.channel is channel and msg.author.id != self.bot.user.id and (re.search('(https?:\/\/[^ ]*\.(?:gif|png|jpg|jpeg))', msg.content) is not None or msg.attachments or msg.embeds):
+                return True
+            return False
+
+        try:
+            while self.reversifying:
+                message = await self.bot.wait_for('message', check=check)
+                await self.reversiqueue.put(message)
+                await message.add_reaction('\N{HOURGLASS WITH FLOWING SAND}')
+
+        except exc.Abort:
+            u.tasks['auto_rev'].remove(channel.id)
+            u.dump(u.tasks, 'cogs/tasks.pkl')
+            if not u.tasks['auto_rev']:
+                self.reversifying = False
+            print('STOPPED : reversifying #{}'.format(channel.name))
+            await channel.send('**Stopped queueing messages for reversification in** {}'.format(channel.mention), delete_after=5)
+
+    @cmds.command(name='autoreversify', aliases=['autorev'])
+    @cmds.has_permissions(manage_channels=True)
+    async def auto_reversify(self, ctx):
+        if ctx.channel.id not in u.tasks['auto_rev']:
+            u.tasks['auto_rev'].append(ctx.channel.id)
+            u.dump(u.tasks, 'cogs/tasks.pkl')
+            self.bot.loop.create_task(
+                self.queue_for_reversification(ctx.channel))
+            if not self.reversifying:
+                self.bot.loop.create_task(self._reversify())
+                self.reversifying = True
+
+            print('STARTED : auto-reversifying in #{}'.format(ctx.channel.name))
+            await ctx.send('**Auto-reversifying all images in** {}'.format(ctx.channel.mention), delete_after=5)
+        else:
+            await ctx.send('**Already auto-reversifying in {}.** Type `stop r(eversifying)` to stop.'.format(ctx.channel.mention), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    async def _get_pool(self, ctx, *, destination, booru='e621', query=[]):
+        def on_reaction(reaction, user):
+            if reaction.emoji == '\N{OCTAGONAL SIGN}' and reaction.message.id == ctx.message.id and user is ctx.author:
+                raise exc.Abort(match)
+            return False
+
+        def on_message(msg):
+            return msg.content.isdigit() and int(msg.content) <= len(pools) and int(msg.content) > 0 and msg.author is ctx.author and msg.channel is ctx.channel
+
+        posts = {}
+        pool = {}
+
+        try:
+            pools = []
+            pool_request = await u.fetch('https://{}.net/pool/index.json'.format(booru), params={'query': ' '.join(query)}, json=True)
+            if len(pool_request) > 1:
+                for pool in pool_request:
+                    pools.append(pool['name'])
+                match = await ctx.send('**Multiple pools found for `{}`.** Type the number of the correct match.\n```\n{}```'.format(' '.join(query), '\n'.join(['{} {}'.format(c, elem) for c, elem in enumerate(pools, 1)])))
+
+                await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
+                done, pending = await asyncio.wait([self.bot.wait_for('reaction_add', check=on_reaction, timeout=60),
+                                                    self.bot.wait_for('reaction_remove', check=on_reaction, timeout=60), self.bot.wait_for('message', check=on_message, timeout=60)], return_when=asyncio.FIRST_COMPLETED)
+                for future in done:
+                    selection = future.result()
+
+                await match.delete()
+                tempool = [pool for pool in pool_request if pool['name']
+                           == pools[int(selection.content) - 1]][0]
+                await selection.delete()
+                pool = {'name': tempool['name'], 'id': tempool['id']}
+
+                await destination.trigger_typing()
+            elif pool_request:
+                tempool = pool_request[0]
+                pool = {'name': pool_request[0]
+                        ['name'], 'id': pool_request[0]['id']}
+            else:
+                raise exc.NotFound
+
+            page = 1
+            while len(posts) < tempool['post_count']:
+                posts_request = await u.fetch('https://{}.net/pool/show.json'.format(booru), params={'id': tempool['id'], 'page': page}, json=True)
+                for post in posts_request['posts']:
+                    posts[post['id']] = {'artist': ', '.join(
+                        post['artist']), 'file_url': post['file_url'], 'score': post['score']}
+                page += 1
+
+            return pool, posts
+
+        except exc.Abort as e:
+            await e.message.edit(content='\N{NO ENTRY SIGN}')
+            raise exc.Continue
+
+    # Messy code that checks image limit and tags in blacklists
+    async def _get_posts(self, ctx, *, booru='e621', tags=[], limit=1, previous={}):
+        guild = ctx.guild if isinstance(
+            ctx.guild, d.Guild) else ctx.channel
+
+        blacklist = set()
+        # Creates temp blacklist based on context
+        for bl in (self.blacklists['global_blacklist'], self.blacklists['guild_blacklist'].get(guild.id, {}).get(ctx.channel.id, set()), self.blacklists['user_blacklist'].get(ctx.author.id, set())):
+            for tag in bl:
+                blacklist.update([tag] + list(self.aliases[tag]))
+        # Checks for, assigns, and removes first order in tags if possible
+        order = [tag for tag in tags if 'order:' in tag]
+        if order:
+            order = order[0]
+            tags.remove(order)
+        else:
+            order = 'order:random'
+        # Checks if tags are in local blacklists
+        if tags:
+            if (len(tags) > 5 and booru == 'e621') or (len(tags) > 4 and booru == 'e926'):
+                raise exc.TagBoundsError(formatter.tostring(tags[5:]))
+            for tag in tags:
+                if tag == 'swf' or tag == 'webm' or tag in blacklist:
+                    raise exc.TagBlacklisted(tag)
+
+        # Checks for blacklisted tags in endpoint blacklists - try/except is for continuing the parent loop
+        posts = {}
+        temposts = len(posts)
+        empty = 0
+        c = 0
+        while len(posts) < limit:
+            if c == limit * 5 + (self.LIMIT / 5):
+                raise exc.Timeout
+            request = await u.fetch('https://{}.net/post/index.json'.format(booru), params={'tags': ','.join([order] + tags), 'limit': int(self.LIMIT * limit)}, json=True)
+            if len(request) == 0:
+                raise exc.NotFound(formatter.tostring(tags))
+            if len(request) < limit:
+                limit = len(request)
+
+            for post in request:
+                if 'swf' in post['file_ext'] or 'webm' in post['file_ext']:
+                    continue
+                try:
+                    for tag in blacklist:
+                        if tag in post['tags']:
+                            raise exc.Continue
+                except exc.Continue:
+                    continue
+                if post['id'] not in posts.keys() and post['id'] not in previous.keys():
+                    posts[post['id']] = {'artist': ', '.join(
+                        post['artist']), 'file_url': post['file_url'], 'score': post['score']}
+                if len(posts) == limit:
+                    break
+
+            if len(posts) == temposts:
+                empty += 1
+                if empty == 5:
+                    break
+            else:
+                empty = 0
+                temposts = len(posts)
+                c += 1
+
+        if posts:
+            return posts, order
+        else:
+            raise exc.NotFound(formatter.tostring(tags))
+
+    # Creates reaction-based paginator for linked pools
+    @cmds.command(name='poolpage', aliases=['poolp', 'pp', 'e621pp', 'e6pp', '6pp'], brief='e621 pool paginator', description='e621 | NSFW\nShow pools in a page format')
+    async def pool_paginator(self, ctx, *args):
+        def on_reaction(reaction, user):
+            if reaction.emoji == '\N{OCTAGONAL SIGN}' and reaction.message.id == ctx.message.id and (user is ctx.author or user.permissions_in(reaction.message.channel).manage_messages):
+                raise exc.Abort
+            elif reaction.emoji == '\N{HEAVY BLACK HEART}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.Save
+            elif reaction.emoji == '\N{LEFTWARDS BLACK ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.Left
+            elif reaction.emoji == '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.GoTo
+            elif reaction.emoji == '\N{BLACK RIGHTWARDS ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.Right
+            return False
+
+        def on_message(msg):
+            return msg.content.isdigit() and 0 <= int(msg.content) <= len(posts) and msg.author is ctx.author and msg.channel is ctx.channel
+
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, query = kwargs['destination'], kwargs['remaining']
+            hearted = {}
+            c = 1
+
+            await dest.trigger_typing()
+
+            pool, posts = await self._get_pool(ctx, destination=dest, booru='e621', query=query)
+            keys = list(posts.keys())
+            values = list(posts.values())
+
+            embed = d.Embed(
+                title=values[c - 1]['artist'], url='https://e621.net/post/show/{}'.format(keys[c - 1]), color=dest.me.color if isinstance(dest.channel, d.TextChannel) else u.color)
+            embed.set_image(url=values[c - 1]['file_url'])
+            embed.set_author(name=pool['name'],
+                             url='https://e621.net/pool/show?id={}'.format(pool['id']), icon_url=ctx.author.avatar_url)
+            embed.set_footer(text='{} / {}'.format(c, len(posts)),
+                             icon_url=self._get_score(values[c - 1]['score']))
+
+            paginator = await dest.send(embed=embed)
+
+            for emoji in ('\N{HEAVY BLACK HEART}', '\N{LEFTWARDS BLACK ARROW}', '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}', '\N{BLACK RIGHTWARDS ARROW}'):
+                await paginator.add_reaction(emoji)
+            await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
+            await asyncio.sleep(1)
+
+            while not self.bot.is_closed():
+                try:
+                    await asyncio.gather(*[self.bot.wait_for('reaction_add', check=on_reaction, timeout=7 * 60),
+                                           self.bot.wait_for('reaction_remove', check=on_reaction, timeout=7 * 60)])
+
+                except exc.Save:
+                    if keys[c - 1] not in hearted:
+                        hearted[keys[c - 1]] = copy.deepcopy(embed)
+
+                        await paginator.edit(content='\N{HEAVY BLACK HEART}')
+                    else:
+                        del hearted[keys[c - 1]]
+
+                        await paginator.edit(content='\N{BROKEN HEART}')
+
+                except exc.Left:
+                    if c > 1:
+                        c -= 1
+                        embed.title = values[c - 1]['artist']
+                        embed.url = 'https://e621.net/post/show/{}'.format(
+                            keys[c - 1])
+                        embed.set_footer(text='{} / {}'.format(c, len(posts)),
+                                         icon_url=self._get_score(values[c - 1]['score']))
+                        embed.set_image(url=values[c - 1]['file_url'])
+
+                        await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
+                    else:
+                        await paginator.edit(content='\N{BLACK RIGHTWARDS ARROW}')
+
+                except exc.GoTo:
+                    await paginator.edit(content='\N{INPUT SYMBOL FOR NUMBERS}')
+                    number = await self.bot.wait_for('message', check=on_message, timeout=7 * 60)
+
+                    if int(number.content) != 0:
+                        c = int(number.content)
+
+                        embed.title = values[c - 1]['artist']
+                        embed.url = 'https://e621.net/post/show/{}'.format(
+                            keys[c - 1])
+                        embed.set_footer(text='{} / {}'.format(c, len(posts)),
+                                         icon_url=self._get_score(values[c - 1]['score']))
+                        embed.set_image(url=values[c - 1]['file_url'])
+
+                    await number.delete()
+
+                    await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
+
+                except exc.Right:
+                    if c < len(keys):
+                        c += 1
+                        embed.title = values[c - 1]['artist']
+                        embed.url = 'https://e621.net/post/show/{}'.format(
+                            keys[c - 1])
+                        embed.set_footer(text='{} / {}'.format(c, len(posts)),
+                                         icon_url=self._get_score(values[c - 1]['score']))
+                        embed.set_image(url=values[c - 1]['file_url'])
+
+                        await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
+                    else:
+                        await paginator.edit(content='\N{LEFTWARDS BLACK ARROW}')
+
+        except exc.Abort:
+            try:
+                await paginator.edit(content='\N{WHITE HEAVY CHECK MARK}')
+            except UnboundLocalError:
+                await dest.send('\N{WHITE HEAVY CHECK MARK}')
+        except asyncio.TimeoutError:
+            try:
+                await paginator.edit(content='\N{HOURGLASS}')
+            except UnboundLocalError:
+                await dest.send('\N{HOURGLASS}')
+        except exc.NotFound:
+            await ctx.send('**Pool not found**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.Timeout:
+            await ctx.send('**Request timed out**')
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.Continue:
+            pass
+
+        finally:
+            if hearted:
+                await ctx.message.add_reaction('\N{HOURGLASS WITH FLOWING SAND}')
+
+                n = 1
+                for embed in hearted.values():
+                    await asyncio.sleep(self.RATE_LIMIT)
+
+                    await ctx.author.send(content=f'`{n} / {len(hearted)}`', embed=embed)
+                    n += 1
+
+    @cmds.command(name='e621page', aliases=['e621p', 'e6p', '6p'])
+    @checks.is_nsfw()
+    async def e621_paginator(self, ctx, *args):
+        def on_reaction(reaction, user):
+            if reaction.emoji == '\N{OCTAGONAL SIGN}' and reaction.message.id == ctx.message.id and (user is ctx.author or user.permissions_in(reaction.message.channel).manage_messages):
+                raise exc.Abort
+            elif reaction.emoji == '\N{HEAVY BLACK HEART}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.Save
+            elif reaction.emoji == '\N{LEFTWARDS BLACK ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.Left
+            elif reaction.emoji == '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.GoTo
+            elif reaction.emoji == '\N{BLACK RIGHTWARDS ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.Right
+            return False
+
+        def on_message(msg):
+            return msg.content.isdigit() and 0 <= int(msg.content) <= len(posts) and msg.author is ctx.author and msg.channel is ctx.channel
+
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, tags = kwargs['destination'], kwargs['remaining']
+            limit = self.LIMIT / 5
+            hearted = {}
+            c = 1
+
+            tags = self._get_favorites(ctx, tags)
+
+            await ctx.trigger_typing()
+
+            posts, order = await self._get_posts(ctx, booru='e621', tags=tags, limit=limit)
+            keys = list(posts.keys())
+            values = list(posts.values())
+
+            embed = d.Embed(
+                title=values[c - 1]['artist'], url='https://e621.net/post/show/{}'.format(keys[c - 1]), color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
+            embed.set_image(url=values[c - 1]['file_url'])
+            embed.set_author(name=formatter.tostring(tags, order=order),
+                             url='https://e621.net/post?tags={}'.format(','.join(tags)), icon_url=ctx.author.avatar_url)
+            embed.set_footer(text=values[c - 1]['score'],
+                             icon_url=self._get_score(values[c - 1]['score']))
+
+            paginator = await dest.send(embed=embed)
+
+            for emoji in ('\N{HEAVY BLACK HEART}', '\N{LEFTWARDS BLACK ARROW}', '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}', '\N{BLACK RIGHTWARDS ARROW}'):
+                await paginator.add_reaction(emoji)
+            await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
+            await asyncio.sleep(1)
+
+            while not self.bot.is_closed():
+                try:
+                    await asyncio.gather(*[self.bot.wait_for('reaction_add', check=on_reaction, timeout=7 * 60),
+                                           self.bot.wait_for('reaction_remove', check=on_reaction, timeout=7 * 60)])
+
+                except exc.Save:
+                    if keys[c - 1] not in hearted.keys():
+                        hearted[keys[c - 1]] = copy.deepcopy(embed)
+
+                        await paginator.edit(content='\N{HEAVY BLACK HEART}')
+                    else:
+                        del hearted[keys[c - 1]]
+
+                        await paginator.edit(content='\N{BROKEN HEART}')
+
+                except exc.Left:
+                    if c > 1:
+                        c -= 1
+                        embed.title = values[c - 1]['artist']
+                        embed.url = 'https://e621.net/post/show/{}'.format(
+                            keys[c - 1])
+                        embed.set_footer(text=values[c - 1]['score'],
+                                         icon_url=self._get_score(values[c - 1]['score']))
+                        embed.set_image(url=values[c - 1]['file_url'])
+
+                        await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
+                    else:
+                        await paginator.edit(content='\N{BLACK RIGHTWARDS ARROW}')
+
+                except exc.GoTo:
+                    await paginator.edit(content=f'`{c} / {len(posts)}`')
+                    number = await self.bot.wait_for('message', check=on_message, timeout=7 * 60)
+
+                    if int(number.content) != 0:
+                        c = int(number.content)
+
+                        embed.title = values[c - 1]['artist']
+                        embed.url = 'https://e621.net/post/show/{}'.format(
+                            keys[c - 1])
+                        embed.set_footer(text=values[c - 1]['score'],
+                                         icon_url=self._get_score(values[c - 1]['score']))
+                        embed.set_image(url=values[c - 1]['file_url'])
+
+                    await number.delete()
+
+                    await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
+
+                except exc.Right:
+                    try:
+                        if c % limit == 0:
+                            await dest.trigger_typing()
+                            temposts, order = await self._get_posts(ctx, booru='e621', tags=tags, limit=limit, previous=posts)
+                            posts.update(temposts)
+
+                            keys = list(posts.keys())
+                            values = list(posts.values())
+
+                        if c < len(keys):
+                            c += 1
+                            embed.title = values[c - 1]['artist']
+                            embed.url = 'https://e621.net/post/show/{}'.format(
+                                keys[c - 1])
+                            embed.set_footer(text=values[c - 1]['score'],
+                                             icon_url=self._get_score(values[c - 1]['score']))
+                            embed.set_image(url=values[c - 1]['file_url'])
+
+                            await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
+                        else:
+                            await paginator.edit(content='\N{LEFTWARDS BLACK ARROW}')
+
+                    except exc.NotFound:
+                        await paginator.edit(content='\N{LEFTWARDS BLACK ARROW}')
+
+        except exc.Abort:
+            try:
+                await paginator.edit(content='\N{WHITE HEAVY CHECK MARK}')
+            except UnboundLocalError:
+                await dest.send('\N{HOURGLASS}')
+        except asyncio.TimeoutError:
+            try:
+                await paginator.edit(content='\N{HOURGLASS}')
+            except UnboundLocalError:
+                await dest.send('\N{HOURGLASS}')
+        except exc.NotFound as e:
+            await ctx.send('`{}` **not found**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.TagBlacklisted as e:
+            await ctx.send('\N{NO ENTRY SIGN} `{}` **blacklisted**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{NO ENTRY SIGN}')
+        except exc.TagBoundsError as e:
+            await ctx.send('`{}` **out of bounds.** Tags limited to 5.'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.FavoritesNotFound:
+            await ctx.send('**You have no favorite tags**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.Timeout:
+            await ctx.send('**Request timed out**')
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+        finally:
+            if hearted:
+                await ctx.message.add_reaction('\N{HOURGLASS WITH FLOWING SAND}')
+
+                n = 1
+                for embed in hearted.values():
+                    await asyncio.sleep(self.RATE_LIMIT)
+
+                    await ctx.author.send(content=f'`{n} / {len(hearted)}`', embed=embed)
+                    n += 1
+
+    # @e621_paginator.error
+    # async def e621_paginator_error(self, ctx, error):
+    #     if isinstance(error, exc.NSFW):
+    #         await ctx.send('\N{NO ENTRY} {} **is not an NSFW channel**'.format(ctx.channel.mention), delete_after=7)
+    #         await ctx.message.add_reaction('\N{NO ENTRY}')
+
+    @cmds.command(name='e926page', aliases=['e926p', 'e9p', '9p'])
+    async def e926_paginator(self, ctx, *args):
+        def on_reaction(reaction, user):
+            if reaction.emoji == '\N{OCTAGONAL SIGN}' and reaction.message.id == ctx.message.id and (user is ctx.author or user.permissions_in(reaction.message.channel).manage_messages):
+                raise exc.Abort
+            elif reaction.emoji == '\N{HEAVY BLACK HEART}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.Save
+            elif reaction.emoji == '\N{LEFTWARDS BLACK ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.Left
+            elif reaction.emoji == '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.GoTo
+            elif reaction.emoji == '\N{BLACK RIGHTWARDS ARROW}' and reaction.message.id == paginator.id and user is ctx.author:
+                raise exc.Right
+            return False
+
+        def on_message(msg):
+            return msg.content.isdigit() and 0 <= int(msg.content) <= len(posts) and msg.author is ctx.author and msg.channel is ctx.channel
+
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, tags = kwargs['destination'], kwargs['remaining']
+            limit = self.LIMIT / 5
+            hearted = {}
+            c = 1
+
+            tags = self._get_favorites(ctx, tags)
+
+            await ctx.trigger_typing()
+
+            posts, order = await self._get_posts(ctx, booru='e926', tags=tags, limit=limit)
+            keys = list(posts.keys())
+            values = list(posts.values())
+
+            embed = d.Embed(
+                title=values[c - 1]['artist'], url='https://e926.net/post/show/{}'.format(keys[c - 1]), color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
+            embed.set_image(url=values[c - 1]['file_url'])
+            embed.set_author(name=formatter.tostring(tags, order=order),
+                             url='https://e926.net/post?tags={}'.format(','.join(tags)), icon_url=ctx.author.avatar_url)
+            embed.set_footer(text=values[c - 1]['score'],
+                             icon_url=self._get_score(values[c - 1]['score']))
+
+            paginator = await dest.send(embed=embed)
+
+            for emoji in ('\N{HEAVY BLACK HEART}', '\N{LEFTWARDS BLACK ARROW}', '\N{NUMBER SIGN}\N{COMBINING ENCLOSING KEYCAP}', '\N{BLACK RIGHTWARDS ARROW}'):
+                await paginator.add_reaction(emoji)
+            await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
+            await asyncio.sleep(1)
+
+            while not self.bot.is_closed():
+                try:
+                    await asyncio.gather(*[self.bot.wait_for('reaction_add', check=on_reaction, timeout=7 * 60),
+                                           self.bot.wait_for('reaction_remove', check=on_reaction, timeout=7 * 60)])
+
+                except exc.Save:
+                    if keys[c - 1] not in hearted:
+                        hearted[keys[c - 1]] = copy.deepcopy(embed)
+
+                        await paginator.edit(content='\N{HEAVY BLACK HEART}')
+                    else:
+                        del hearted[keys[c - 1]]
+
+                        await paginator.edit(content='\N{BROKEN HEART}')
+
+                except exc.Left:
+                    if c > 1:
+                        c -= 1
+                        embed.title = values[c - 1]['artist']
+                        embed.url = 'https://e926.net/post/show/{}'.format(
+                            keys[c - 1])
+                        embed.set_footer(text=values[c - 1]['score'],
+                                         icon_url=self._get_score(values[c - 1]['score']))
+                        embed.set_image(url=values[c - 1]['file_url'])
+
+                        await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
+                    else:
+                        await paginator.edit(content='\N{BLACK RIGHTWARDS ARROW}')
+
+                except exc.GoTo:
+                    await paginator.edit(content=f'`{c} / {len(posts)}`')
+                    number = await self.bot.wait_for('message', check=on_message, timeout=7 * 60)
+
+                    if int(number.content) != 0:
+                        c = int(number.content)
+
+                        embed.title = values[c - 1]['artist']
+                        embed.url = 'https://e926.net/post/show/{}'.format(
+                            keys[c - 1])
+                        embed.set_footer(text=values[c - 1]['score'],
+                                         icon_url=self._get_score(values[c - 1]['score']))
+                        embed.set_image(url=values[c - 1]['file_url'])
+
+                    await number.delete()
+
+                    await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
+
+                except exc.Right:
+                    try:
+                        if c % limit == 0:
+                            await dest.trigger_typing()
+                            temposts, order = await self._get_posts(ctx, booru='e926', tags=tags, limit=limit, previous=posts)
+                            posts.update(temposts)
+
+                            keys = list(posts.keys())
+                            values = list(posts.values())
+
+                        if c < len(keys):
+                            c += 1
+                            embed.title = values[c - 1]['artist']
+                            embed.url = 'https://e926.net/post/show/{}'.format(
+                                keys[c - 1])
+                            embed.set_footer(text=values[c - 1]['score'],
+                                             icon_url=self._get_score(values[c - 1]['score']))
+                            embed.set_image(url=values[c - 1]['file_url'])
+
+                            await paginator.edit(content='\N{HEAVY BLACK HEART}' if keys[c - 1] in hearted.keys() else None, embed=embed)
+                        else:
+                            await paginator.edit(content='\N{LEFTWARDS BLACK ARROW}')
+
+                    except exc.NotFound:
+                        await paginator.edit(content='\N{LEFTWARDS BLACK ARROW}')
+
+        except exc.Abort:
+            try:
+                await paginator.edit(content='\N{WHITE HEAVY CHECK MARK}')
+            except UnboundLocalError:
+                await dest.send('\N{WHITE HEAVY CHECK MARK}')
+        except asyncio.TimeoutError:
+            try:
+                await paginator.edit(content='\N{HOURGLASS}')
+            except UnboundLocalError:
+                await dest.send('\N{HOURGLASS}')
+        except exc.NotFound as e:
+            await ctx.send('`{}` **not found**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.TagBlacklisted as e:
+            await ctx.send('\N{NO ENTRY SIGN} `{}` **blacklisted**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{NO ENTRY SIGN}')
+        except exc.TagBoundsError as e:
+            await ctx.send('`{}` **out of bounds.** Tags limited to 5.'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.FavoritesNotFound:
+            await ctx.send('**You have no favorite tags**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.Timeout:
+            await ctx.send('**Request timed out**')
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+        finally:
+            if hearted:
+                await ctx.message.add_reaction('\N{HOURGLASS WITH FLOWING SAND}')
+
+                n = 1
+                for embed in hearted.values():
+                    await asyncio.sleep(self.RATE_LIMIT)
+
+                    await ctx.author.send(content=f'`{n} / {len(hearted)}`', embed=embed)
+                    n += 1
+
+    # Searches for and returns images from e621.net given tags when not blacklisted
+    @cmds.command(aliases=['e6', '6'], brief='e621 | NSFW', description='e621 | NSFW\nTag-based search for e621.net\n\nYou can only search 5 tags and 6 images at once for now.\ne6 [tags...] ([# of images])')
+    @checks.is_nsfw()
+    async def e621(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args, limit=3)
+            dest, args, limit = kwargs['destination'], kwargs['remaining'], kwargs['limit']
+
+            tags = self._get_favorites(ctx, args)
+
+            await dest.trigger_typing()
+
+            posts, order = await self._get_posts(ctx, booru='e621', tags=tags, limit=limit)
+
+            for ident, post in posts.items():
+                embed = d.Embed(title=post['artist'], url='https://e621.net/post/show/{}'.format(ident),
+                                color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
+                embed.set_image(url=post['file_url'])
+                embed.set_author(name=formatter.tostring(tags, order=order),
+                                 url='https://e621.net/post?tags={}'.format(','.join(tags)), icon_url=ctx.author.avatar_url)
+                embed.set_footer(
+                    text=post['score'], icon_url=self._get_score(post['score']))
+
+                message = await dest.send(embed=embed)
+
+                self.bot.loop.create_task(self.queue_for_hearts(message=message, send=embed))
+
+        except exc.TagBlacklisted as e:
+            await ctx.send('`{}` **blacklisted**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.BoundsError as e:
+            await ctx.send('`{}` **out of bounds.** Images limited to 3.'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.TagBoundsError as e:
+            await ctx.send('`{}` **out of bounds.** Tags limited to 5.'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.NotFound as e:
+            await ctx.send('`{}` **not found**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.FavoritesNotFound:
+            await ctx.send('**You have no favorite tags**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.Timeout:
+            await ctx.send('**Request timed out**')
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    # @e621.error
+    # async def e621_error(self, ctx, error):
+    #     if isinstance(error, exc.NSFW):
+    #         await ctx.send('\N{NO ENTRY} {} **is not an NSFW channel**'.format(ctx.channel.mention), delete_after=7)
+    #         await ctx.message.add_reaction('\N{NO ENTRY}')
+
+    # Searches for and returns images from e926.net given tags when not blacklisted
+    @cmds.command(aliases=['e9', '9'], brief='e926 | SFW', description='e926 | SFW\nTag-based search for e926.net\n\nYou can only search 5 tags and 6 images at once for now.\ne9 [tags...] ([# of images])')
+    async def e926(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args, limit=3)
+            dest, args, limit = kwargs['destination'], kwargs['remaining'], kwargs['limit']
+
+            tags = self._get_favorites(ctx, args)
+
+            await dest.trigger_typing()
+
+            posts, order = await self._get_posts(ctx, booru='e926', tags=tags, limit=limit)
+
+            for ident, post in posts.items():
+                embed = d.Embed(title=post['artist'], url='https://e926.net/post/show/{}'.format(ident),
+                                color=ctx.me.color if isinstance(ctx.channel, d.TextChannel) else u.color)
+                embed.set_image(url=post['file_url'])
+                embed.set_author(name=formatter.tostring(tags, order=order),
+                                 url='https://e621.net/post?tags={}'.format(','.join(tags)), icon_url=ctx.author.avatar_url)
+                embed.set_footer(
+                    text=post['score'], icon_url=self._get_score(post['score']))
+
+                message = await dest.send(embed=embed)
+
+                self.bot.loop.create_task(self.queue_for_hearts(message=message, send=embed))
+
+        except exc.TagBlacklisted as e:
+            await ctx.send('`{}` **blacklisted**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.BoundsError as e:
+            await ctx.send('`{}` **out of bounds.** Images limited to 3.'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.TagBoundsError as e:
+            await ctx.send('`{}` **out of bounds.** Tags limited to 5.'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.NotFound as e:
+            await ctx.send('`{}` **not found**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.FavoritesNotFound:
+            await ctx.send('**You have no favorite tags**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.Timeout:
+            await ctx.send('**Request timed out**')
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @cmds.group(aliases=['fave', 'fav', 'f'])
+    async def favorite(self, ctx):
+        if not ctx.invoked_subcommand:
+            await ctx.send('**Use a flag to manage favorites.**\n*Type* `{}help fav` *for more info.*'.format(ctx.prefix), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @favorite.error
+    async def favorite_error(self, ctx, error):
+        pass
+
+    @favorite.group(name='get', aliases=['g'])
+    async def _get_favorite(self, ctx):
+        pass
+
+    @_get_favorite.command(name='tags', aliases=['t'])
+    async def __get_favorite_tags(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        await dest.send('\N{WHITE MEDIUM STAR} {}**\'s favorite tags:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(self.favorites.get(ctx.author.id, {}).get('tags', set()))), delete_after=7)
+
+    @_get_favorite.command(name='posts', aliases=['p'])
+    async def __get_favorite_posts(self, ctx):
+        pass
+
+    @favorite.group(name='add', aliases=['a'])
+    async def _add_favorite(self, ctx):
+        pass
+
+    @_add_favorite.command(name='tags', aliases=['t'])
+    async def __add_favorite_tags(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, tags = kwargs['destination'], kwargs['remaining']
+
+            for tag in tags:
+                if tag in self.blacklists['user_blacklist'].get(ctx.author.id, set()):
+                    raise exc.TagBlacklisted(tag)
+            with suppress(KeyError):
+                if len(self.favorites[ctx.author.id]['tags']) + len(tags) > 5:
+                    raise exc.BoundsError
+
+            self.favorites.setdefault(ctx.author.id, {}).setdefault(
+                'tags', set()).update(tags)
+            u.dump(self.favorites, 'cogs/favorites.pkl')
+
+            await dest.send('{} **added to their favorites:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(tags)), delete_after=5)
+
+        except exc.BoundsError:
+            await ctx.send('**Favorites list currently limited to:** `5`', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.TagBlacklisted as e:
+            await ctx.send('\N{NO ENTRY SIGN} `{}` **blacklisted**', delete_after=7)
+            await ctx.message.add_reaction('\N{NO ENTRY SIGN}')
+
+    @_add_favorite.command(name='posts', aliases=['p'])
+    async def __add_favorite_posts(self, ctx, *posts):
+        pass
+
+    @favorite.group(name='remove', aliases=['r'])
+    async def _remove_favorite(self, ctx):
+        pass
+
+    @_remove_favorite.command(name='tags', aliases=['t'])
+    async def __remove_favorite_tags(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, tags = kwargs['destination'], kwargs['remaining']
+
+            for tag in tags:
+                try:
+                    self.favorites[ctx.author.id].get(
+                        'tags', set()).remove(tag)
+
+                except KeyError:
+                    raise exc.TagError(tag)
+
+            u.dump(self.favorites, 'cogs/favorites.pkl')
+
+            await dest.send('{} **removed from their favorites:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(tags)), delete_after=5)
+
+        except KeyError:
+            await ctx.send('**You do not have any favorites**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except exc.TagError as e:
+            await ctx.send('`{}` **not in favorites**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @_remove_favorite.command(name='posts', aliases=['p'])
+    async def __remove_favorite_posts(self, ctx):
+        pass
+
+    @favorite.group(name='clear', aliases=['c'])
+    async def _clear_favorite(self, ctx):
+        pass
+
+    @_clear_favorite.command(name='tags', aliases=['t'])
+    async def __clear_favorite_tags(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        with suppress(KeyError):
+            del self.favorites[ctx.author.id]
+            u.dump(self.favorites, 'cogs/favorites.pkl')
+
+        await dest.send('{}**\'s favorites cleared**'.format(ctx.author.mention), delete_after=5)
+
+    @_clear_favorite.command(name='posts', aliases=['p'])
+    async def __clear_favorite_posts(self, ctx):
+        pass
+
+    # Umbrella command structure to manage global, channel, and user blacklists
+    @cmds.group(aliases=['bl', 'b'], brief='(G) Manage blacklists', description='Manage channel or personal blacklists\n\nUsage:\n\{p\}bl get \{blacklist\} to show a blacklist\n\{p\}bl clear \{blacklist\} to clear a blacklist\n\{p\}bl add \{blacklist\} \{tags...\} to add tag(s) to a blacklist\n\{p\}bl remove \{blacklist\} \{tags...\} to remove tags from a blacklist')
+    async def blacklist(self, ctx):
+        if not ctx.invoked_subcommand:
+            await ctx.send('**Use a flag to manage blacklists.**\n*Type* `{}help bl` *for more info.*'.format(ctx.prefix), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    # @blacklist.error
+    # async def blacklist_error(self, ctx, error):
+        # if isinstance(error, KeyError):
+        #     return await ctx.send('**Blacklist does not exist**', delete_after=7)
+
+    @blacklist.group(name='get', aliases=['g'], brief='(G) Get a blacklist\n\nUsage:\n\{p\}bl get \{blacklist\}')
+    async def _get_blacklist(self, ctx):
+        if not ctx.invoked_subcommand:
+            await ctx.send('**Invalid blacklist**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @_get_blacklist.command(name='global', aliases=['gl', 'g'], brief='Get current global blacklist', description='Get current global blacklist\n\nThis applies to all booru commands, in accordance with Discord\'s ToS agreement\n\nExample:\n\{p\}bl get global')
+    async def __get_global_blacklist(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        await dest.send('\N{NO ENTRY SIGN} **Global blacklist:**\n```\n{}```'.format(formatter.tostring(self.blacklists['global_blacklist'])))
+
+    @_get_blacklist.command(name='channel', aliases=['ch', 'c'], brief='Get current channel blacklist', description='Get current channel blacklist\n\nThis is based on context - the channel where the command was executed\n\nExample:\{p\}bl get channel')
+    async def __get_channel_blacklist(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        guild = ctx.guild if isinstance(
+            ctx.guild, d.Guild) else ctx.channel
+
+        await dest.send('\N{NO ENTRY SIGN} {} **blacklist:**\n```\n{}```'.format(ctx.channel.mention, formatter.tostring(self.blacklists['guild_blacklist'].get(guild.id, {}).get(ctx.channel.id, set()))))
+
+    @_get_blacklist.command(name='me', aliases=['m'], brief='Get your personal blacklist', description='Get your personal blacklist\n\nYour blacklist is not viewable by anyone but you, except if you call this command in a public channel. The blacklist will be deleted soon after for your privacy\n\nExample:\n\{p\}bl get me')
+    async def __get_user_blacklist(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        await dest.send('\N{NO ENTRY SIGN} {}**\'s blacklist:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(self.blacklists['user_blacklist'].get(ctx.author.id, set()))), delete_after=7)
+
+    @_get_blacklist.command(name='here', aliases=['h'], brief='Get current global and channel blacklists', description='Get current global and channel blacklists in a single message\n\nExample:\{p\}bl get here')
+    async def __get_here_blacklists(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        guild = ctx.guild if isinstance(
+            ctx.guild, d.Guild) else ctx.channel
+
+        await dest.send('\N{NO ENTRY SIGN} **__Blacklisted:__**\n\n**Global:**\n```\n{}```\n**{}:**\n```\n{}```'.format(formatter.tostring(self.blacklists['global_blacklist']), ctx.channel.mention, formatter.tostring(self.blacklists['guild_blacklist'].get(guild.id, {}).get(ctx.channel.id, set()))))
+
+    @_get_blacklist.group(name='all', aliases=['a'], hidden=True)
+    async def __get_all_blacklists(self, ctx):
+        if not ctx.invoked_subcommand:
+            await ctx.send('**Invalid blacklist**')
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @__get_all_blacklists.command(name='guild', aliases=['g'])
+    @cmds.has_permissions(manage_channels=True)
+    async def ___get_all_guild_blacklists(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        guild = ctx.guild if isinstance(
+            ctx.guild, d.Guild) else ctx.channel
+
+        await dest.send('\N{NO ENTRY SIGN} **__{} blacklists:__**\n\n{}'.format(guild.name, formatter.dict_tostring(self.blacklists['guild_blacklist'].get(guild.id, {}))))
+
+    @__get_all_blacklists.command(name='user', aliases=['u', 'member', 'm'])
+    @cmds.is_owner()
+    async def ___get_all_user_blacklists(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        await dest.send('\N{NO ENTRY SIGN} **__User blacklists:__**\n\n{}'.format(formatter.dict_tostring(self.blacklists['user_blacklist'])))
+
+    @blacklist.group(name='add', aliases=['a'], brief='(G) Add tag(s) to a blacklist\n\nUsage:\n\{p\}bl add \{blacklist\} \{tags...\}')
+    async def _add_tags(self, ctx):
+        if not ctx.invoked_subcommand:
+            await ctx.send('**Invalid blacklist**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @_add_tags.command(name='global', aliases=['gl', 'g'])
+    @cmds.is_owner()
+    async def __add_global_tags(self, ctx, *args):
+        kwargs = u.get_kwargs(ctx, args)
+        dest, tags = kwargs['destination'], kwargs['remaining']
+
+        await dest.trigger_typing()
+
+        self.blacklists['global_blacklist'].update(tags)
+        for tag in tags:
+            alias_request = await u.fetch('https://e621.net/tag_alias/index.json', params={'aliased_to': tag, 'approved': 'true'}, json=True)
+            if alias_request:
+                for dic in alias_request:
+                    self.aliases.setdefault(tag, set()).add(dic['name'])
+            else:
+                self.aliases.setdefault(tag, set())
+        u.dump(self.blacklists, 'cogs/blacklists.pkl')
+        u.dump(self.aliases, 'cogs/aliases.pkl')
+
+        await dest.send('**Added to global blacklist:**\n```\n{}```'.format(formatter.tostring(tags)), delete_after=5)
+
+    @_add_tags.command(name='channel', aliases=['ch', 'c'], brief='@manage_channel@ Add tag(s) to the current channel blacklist (requires manage_channel)', description='Add tag(s) to the current channel blacklist ')
+    @cmds.has_permissions(manage_channels=True)
+    async def __add_channel_tags(self, ctx, *args):
+        kwargs = u.get_kwargs(ctx, args)
+        dest, tags = kwargs['destination'], kwargs['remaining']
+
+        guild = ctx.guild if isinstance(
+            ctx.guild, d.Guild) else ctx.channel
+
+        await dest.trigger_typing()
+
+        self.blacklists['guild_blacklist'].setdefault(
+            guild.id, {}).setdefault(ctx.channel.id, set()).update(tags)
+        for tag in tags:
+            alias_request = await u.fetch('https://e621.net/tag_alias/index.json', params={'aliased_to': tag, 'approved': 'true'}, json=True)
+            if alias_request:
+                for dic in alias_request:
+                    self.aliases.setdefault(tag, set()).add(dic['name'])
+            else:
+                self.aliases.setdefault(tag, set())
+        u.dump(self.blacklists, 'cogs/blacklists.pkl')
+        u.dump(self.aliases, 'cogs/aliases.pkl')
+
+        await dest.send('**Added to** {} **blacklist:**\n```\n{}```'.format(ctx.channel.mention, formatter.tostring(tags)), delete_after=5)
+
+    @_add_tags.command(name='me', aliases=['m'])
+    async def __add_user_tags(self, ctx, *args):
+        kwargs = u.get_kwargs(ctx, args)
+        dest, tags = kwargs['destination'], kwargs['remaining']
+
+        await dest.trigger_typing()
+
+        self.blacklists['user_blacklist'].setdefault(
+            ctx.author.id, set()).update(tags)
+        for tag in tags:
+            alias_request = await u.fetch('https://e621.net/tag_alias/index.json', params={'aliased_to': tag, 'approved': 'true'}, json=True)
+            if alias_request:
+                for dic in alias_request:
+                    self.aliases.setdefault(tag, set()).add(dic['name'])
+            else:
+                self.aliases.setdefault(tag, set())
+        u.dump(self.blacklists, 'cogs/blacklists.pkl')
+        u.dump(self.aliases, 'cogs/aliases.pkl')
+
+        await dest.send('{} **added to their blacklist:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(tags)), delete_after=5)
+
+    @blacklist.group(name='remove', aliases=['rm', 'r'])
+    async def _remove_tags(self, ctx):
+        if not ctx.invoked_subcommand:
+            await ctx.send('**Invalid blacklist**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @_remove_tags.command(name='global', aliases=['gl', 'g'])
+    @cmds.is_owner()
+    async def __remove_global_tags(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, tags = kwargs['destination'], kwargs['remaining']
+
+            for tag in tags:
+                try:
+                    self.blacklists['global_blacklist'].remove(tag)
+
+                except KeyError:
+                    raise exc.TagError(tag)
+
+            u.dump(self.blacklists, 'cogs/blacklists.pkl')
+
+            await dest.send('**Removed from global blacklist:**\n```\n{}```'.format(formatter.tostring(tags)), delete_after=5)
+
+        except exc.TagError as e:
+            await ctx.send('`{}` **not in blacklist**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @_remove_tags.command(name='channel', aliases=['ch', 'c'])
+    @cmds.has_permissions(manage_channels=True)
+    async def __remove_channel_tags(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, tags = kwargs['destination'], kwargs['remaining']
+
+            guild = ctx.guild if isinstance(
+                ctx.guild, d.Guild) else ctx.channel
+
+            for tag in tags:
+                try:
+                    self.blacklists['guild_blacklist'][guild.id][ctx.channel.id].remove(
+                        tag)
+
+                except KeyError:
+                    raise exc.TagError(tag)
+
+            u.dump(self.blacklists, 'cogs/blacklists.pkl')
+
+            await dest.send('**Removed from** {} **blacklist:**\n```\n{}```'.format(ctx.channel.mention, formatter.tostring(tags), delete_after=5))
+
+        except exc.TagError as e:
+            await ctx.send('`{}` **not in blacklist**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @_remove_tags.command(name='me', aliases=['m'])
+    async def __remove_user_tags(self, ctx, *args):
+        try:
+            kwargs = u.get_kwargs(ctx, args)
+            dest, tags = kwargs['destination'], kwargs['remaining']
+
+            for tag in tags:
+                try:
+                    self.blacklists['user_blacklist'][ctx.author.id].remove(
+                        tag)
+
+                except KeyError:
+                    raise exc.TagError(tag)
+
+            u.dump(self.blacklists, 'cogs/blacklists.pkl')
+
+            await dest.send('{} **removed from their blacklist:**\n```\n{}```'.format(ctx.author.mention, formatter.tostring(tags)), delete_after=5)
+
+        except exc.TagError as e:
+            await ctx.send('`{}` **not in blacklist**'.format(e), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @blacklist.group(name='clear', aliases=['cl', 'c'])
+    async def _clear_blacklist(self, ctx):
+        if not ctx.invoked_subcommand:
+            await ctx.send('**Invalid blacklist**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @_clear_blacklist.command(name='global', aliases=['gl', 'g'])
+    @cmds.is_owner()
+    async def __clear_global_blacklist(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        self.blacklists['global_blacklist'].clear()
+        u.dump(self.blacklists, 'cogs/blacklists.pkl')
+
+        await dest.send('**Global blacklist cleared**', delete_after=5)
+
+    @_clear_blacklist.command(name='channel', aliases=['ch', 'c'])
+    @cmds.has_permissions(manage_channels=True)
+    async def __clear_channel_blacklist(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        guild = ctx.guild if isinstance(
+            ctx.guild, d.Guild) else ctx.channel
+
+        with suppress(KeyError):
+            del self.blacklists['guild_blacklist'][guild.id][ctx.channel.id]
+            u.dump(self.blacklists, 'cogs/blacklists.pkl')
+
+        await dest.send('{} **blacklist cleared**'.format(ctx.channel.mention), delete_after=5)
+
+    @_clear_blacklist.command(name='me', aliases=['m'])
+    async def __clear_user_blacklist(self, ctx, *args):
+        dest = u.get_kwargs(ctx, args)['destination']
+
+        with suppress(KeyError):
+            del self.blacklists['user_blacklist'][ctx.author.id]
+            u.dump(self.blacklists, 'cogs/blacklists.pkl')
+
+        await dest.send('{}**\'s blacklist cleared**'.format(ctx.author.mention), delete_after=5)
diff --git a/src/cogs/management.py b/src/cogs/management.py
index 7d6b2f2..ec62cce 100644
--- a/src/cogs/management.py
+++ b/src/cogs/management.py
@@ -1,235 +1,235 @@
-import asyncio
-import traceback as tb
-from contextlib import suppress
-from datetime import datetime as dt
-
-import discord as d
-from discord import errors as err
-from discord.ext import commands as cmds
-from discord.ext.commands import errors as errext
-
-from misc import exceptions as exc
-from misc import checks
-from utils import utils as u
-
-
-class Administration:
-
-    def __init__(self, bot):
-        self.bot = bot
-        self.RATE_LIMIT = u.RATE_LIMIT
-        self.queue = asyncio.Queue()
-        self.deleting = False
-
-        if u.tasks['auto_del']:
-            for channel in u.tasks['auto_del']:
-                temp = self.bot.get_channel(channel)
-                self.bot.loop.create_task(self.queue_for_deletion(temp))
-                print('STARTED : auto-deleting in #{}'.format(temp.name))
-            self.deleting = True
-            self.bot.loop.create_task(self.delete())
-
-    @cmds.group(aliases=['pru', 'purge', 'pur', 'clear', 'cl'], hidden=True)
-    @cmds.is_owner()
-    async def prune(self, ctx):
-        pass
-
-    @prune.group(name='user', aliases=['u', 'member', 'm'])
-    async def _prune_user(self, ctx):
-        pass
-
-    @_prune_user.command(name='channel', aliases=['channels', 'chans', 'chan', 'ch', 'c'])
-    async def _prune_user_channel(self, ctx, user: d.User, *channels: d.TextChannel):
-        def confirm(r, u):
-            if u is ctx.author:
-                if r.emoji == '\N{OCTAGONAL SIGN}':
-                    raise exc.Abort
-                if r.emoji == '\N{THUMBS UP SIGN}':
-                    return True
-            return False
-
-        if not channels:
-            channels = [ctx.channel]
-
-        try:
-            pruning = await ctx.send(f'\N{HOURGLASS} **Pruning** {user.mention}**\'s messages from** {"**,** ".join([channel.mention for channel in channels])} **might take some time.** Proceed, {ctx.author.mention}?')
-            await pruning.add_reaction('\N{THUMBS UP SIGN}')
-            await pruning.add_reaction('\N{OCTAGONAL SIGN}')
-            await asyncio.sleep(1)
-
-            await self.bot.wait_for('reaction_add', check=confirm, timeout=10 * 60)
-
-            deleting = await ctx.send(f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages...**')
-            await asyncio.sleep(1)
-
-            c = 0
-            for channel in channels:
-                await deleting.edit(content=f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages from** {channel.mention}')
-
-                deleted = await channel.purge(check=lambda m: m.author.id == user.id, before=pruning, limit=None)
-                c += len(deleted)
-
-            await asyncio.sleep(1)
-
-            for channel in channels:
-                missed = 0
-                async for message in channel.history(before=pruning, limit=None):
-                    if message.author.id == user.id:
-                        missed += 1
-
-                if missed > 0:
-                    await ctx.send(f'\N{DOUBLE EXCLAMATION MARK} `{missed}` **messages were not deleted in** {channel.mention}')
-
-            await ctx.send(f'\N{WHITE HEAVY CHECK MARK} **Finished deleting** `{c}` **of** {user.mention}**\'s messages**')
-
-        except exc.Abort:
-            await ctx.send('**Deletion aborted**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except TimeoutError:
-            await ctx.send('**Deletion timed out**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @_prune_user.command(name='all', aliases=['a'], brief='Prune a user\'s messages from the guild', description='about flag centers on message 50 of 101 messages\n\npfg \{user id\} [before|after|about] [\{message id\}]\n\nExample:\npfg \{user id\} before \{message id\}', hidden=True)
-    @cmds.is_owner()
-    async def _prune_user_all(self, ctx, user: d.User):
-        def confirm(r, u):
-            if u is ctx.author:
-                if r.emoji == '\N{OCTAGONAL SIGN}':
-                    raise exc.Abort
-                if r.emoji == '\N{THUMBS UP SIGN}':
-                    return True
-            return False
-
-        try:
-            pruning = await ctx.send(f'\N{HOURGLASS} **Pruning** {user.mention}**\'s messages might take some time.** Proceed, {ctx.author.mention}?')
-            await pruning.add_reaction('\N{THUMBS UP SIGN}')
-            await pruning.add_reaction('\N{OCTAGONAL SIGN}')
-            await asyncio.sleep(1)
-
-            await self.bot.wait_for('reaction_add', check=confirm, timeout=10 * 60)
-
-            deleting = await ctx.send(f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages...**')
-            await asyncio.sleep(1)
-
-            c = 0
-            for channel in ctx.guild.text_channels:
-                await deleting.edit(content=f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages from** {channel.mention}')
-
-                deleted = await channel.purge(check=lambda m: m.author.id == user.id, before=pruning, limit=None)
-                c += len(deleted)
-
-            await asyncio.sleep(1)
-
-            for channel in ctx.guild.text_channels:
-                missed = 0
-                async for message in channel.history(before=pruning, limit=None):
-                    if message.author.id == user.id:
-                        missed += 1
-
-                if missed > 0:
-                    await ctx.send(f'\N{DOUBLE EXCLAMATION MARK} `{missed}` **messages were not deleted in** {channel.mention}')
-
-            await ctx.send(f'\N{WHITE HEAVY CHECK MARK} **Finished deleting** `{c}` **of** {user.mention}**\'s messages**')
-
-        except exc.Abort:
-            await ctx.send('**Deletion aborted**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-        except TimeoutError:
-            await ctx.send('**Deletion timed out**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @cmds.group(aliases=['task', 'tsk'])
-    async def tasks(self):
-        pass
-
-    async def delete(self):
-        while self.deleting:
-            message = await self.queue.get()
-            await asyncio.sleep(self.RATE_LIMIT)
-            with suppress(err.NotFound):
-                if not message.pinned:
-                    await message.delete()
-
-        print('STOPPED : deleting')
-
-    async def queue_for_deletion(self, channel):
-        def check(msg):
-            if 'stop d' in msg.content.lower() and msg.channel is channel and msg.author.guild_permissions.administrator:
-                raise exc.Abort
-            elif msg.channel is channel and not msg.pinned:
-                return True
-            return False
-
-        try:
-            async for message in channel.history(limit=None):
-                if 'stop d' in message.content.lower() and message.author.guild_permissions.administrator:
-                    raise exc.Abort
-                if not message.pinned:
-                    await self.queue.put(message)
-
-            while not self.bot.is_closed():
-                message = await self.bot.wait_for('message', check=check)
-                await self.queue.put(message)
-
-        except exc.Abort:
-            u.tasks['auto_del'].remove(channel.id)
-            u.dump(u.tasks, 'cogs/tasks.pkl')
-            if not u.tasks['auto_del']:
-                self.deleting = False
-            print('STOPPED : deleting #{}'.format(channel.name))
-            await channel.send('**Stopped queueing messages for deletion in** {}'.format(channel.mention), delete_after=5)
-
-    @cmds.command(name='autodelete', aliases=['autodel'])
-    @cmds.has_permissions(administrator=True)
-    async def auto_delete(self, ctx):
-        try:
-            if ctx.channel.id not in u.tasks['auto_del']:
-                u.tasks['auto_del'].append(ctx.channel.id)
-                u.dump(u.tasks, 'cogs/tasks.pkl')
-                self.bot.loop.create_task(self.queue_for_deletion(ctx.channel))
-                if not self.deleting:
-                    self.bot.loop.create_task(self.delete())
-                    self.deleting = True
-                print('STARTED : auto-deleting in #{}'.format(ctx.channel.name))
-                await ctx.send('**Auto-deleting all messages in {}**'.format(ctx.channel.mention), delete_after=5)
-            else:
-                raise exc.Exists
-
-        except exc.Exists:
-            await ctx.send('**Already auto-deleting in {}.** Type `stop d(eleting)` to stop.'.format(ctx.channel.mention), delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-    @cmds.group(aliases=['setting', 'set', 's'])
-    @cmds.has_permissions(administrator=True)
-    async def settings(self, ctx):
-        pass
-
-    @settings.command(name='deletecommands', aliases=['delcmds', 'delcmd'])
-    async def _settings_deletecommands(self, ctx):
-        if ctx.guild.id not in u.settings['del_ctx']:
-            u.settings['del_ctx'].append(ctx.guild.id)
-        else:
-            u.settings['del_ctx'].remove(ctx.guild.id)
-        u.dump(u.settings, 'settings.pkl')
-
-        await ctx.send('**Delete command invocations:** `{}`'.format(ctx.guild.id in u.settings['del_ctx']))
-
-    @settings.command(name='prefix', aliases=['pre', 'p'])
-    async def _settings_prefix(self, ctx, *prefixes):
-        if prefixes:
-            u.settings['prefixes'][ctx.guild.id] = prefixes
-        else:
-            with suppress(KeyError):
-                del u.settings['prefixes'][ctx.guild.id]
-
-        await ctx.send(f'**Prefix set to:** `{"` or `".join(prefixes if ctx.guild.id in u.settings["prefixes"] else u.config["prefix"])}`')
-
-    @settings.command(name='deleteresponses', aliases=['delresps', 'delresp'])
-    async def _settings_deleteresponses(self, ctx):
-        if ctx.guild.id not in u.settings['del_resp']:
-            u.settings['del_resp'].append(ctx.guild.id)
-        else:
-            u.settings['del_resp'].remove(ctx.guild.id)
-        u.dump(u.settings, 'settings.pkl')
-
-        await ctx.send(f'**Delete command responses:** `{ctx.guild.id in u.settings["del_resp"]}`')
+import asyncio
+import traceback as tb
+from contextlib import suppress
+from datetime import datetime as dt
+
+import discord as d
+from discord import errors as err
+from discord.ext import commands as cmds
+from discord.ext.commands import errors as errext
+
+from misc import exceptions as exc
+from misc import checks
+from utils import utils as u
+
+
+class Administration:
+
+    def __init__(self, bot):
+        self.bot = bot
+        self.RATE_LIMIT = u.RATE_LIMIT
+        self.queue = asyncio.Queue()
+        self.deleting = False
+
+        if u.tasks['auto_del']:
+            for channel in u.tasks['auto_del']:
+                temp = self.bot.get_channel(channel)
+                self.bot.loop.create_task(self.queue_for_deletion(temp))
+                print('STARTED : auto-deleting in #{}'.format(temp.name))
+            self.deleting = True
+            self.bot.loop.create_task(self.delete())
+
+    @cmds.group(aliases=['pru', 'purge', 'pur', 'clear', 'cl'], hidden=True)
+    @cmds.is_owner()
+    async def prune(self, ctx):
+        pass
+
+    @prune.group(name='user', aliases=['u', 'member', 'm'])
+    async def _prune_user(self, ctx):
+        pass
+
+    @_prune_user.command(name='channel', aliases=['channels', 'chans', 'chan', 'ch', 'c'])
+    async def _prune_user_channel(self, ctx, user: d.User, *channels: d.TextChannel):
+        def confirm(r, u):
+            if u is ctx.author:
+                if r.emoji == '\N{OCTAGONAL SIGN}':
+                    raise exc.Abort
+                if r.emoji == '\N{THUMBS UP SIGN}':
+                    return True
+            return False
+
+        if not channels:
+            channels = [ctx.channel]
+
+        try:
+            pruning = await ctx.send(f'\N{HOURGLASS} **Pruning** {user.mention}**\'s messages from** {"**,** ".join([channel.mention for channel in channels])} **might take some time.** Proceed, {ctx.author.mention}?')
+            await pruning.add_reaction('\N{THUMBS UP SIGN}')
+            await pruning.add_reaction('\N{OCTAGONAL SIGN}')
+            await asyncio.sleep(1)
+
+            await self.bot.wait_for('reaction_add', check=confirm, timeout=10 * 60)
+
+            deleting = await ctx.send(f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages...**')
+            await asyncio.sleep(1)
+
+            c = 0
+            for channel in channels:
+                await deleting.edit(content=f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages from** {channel.mention}')
+
+                deleted = await channel.purge(check=lambda m: m.author.id == user.id, before=pruning, limit=None)
+                c += len(deleted)
+
+            await asyncio.sleep(1)
+
+            for channel in channels:
+                missed = 0
+                async for message in channel.history(before=pruning, limit=None):
+                    if message.author.id == user.id:
+                        missed += 1
+
+                if missed > 0:
+                    await ctx.send(f'\N{DOUBLE EXCLAMATION MARK} `{missed}` **messages were not deleted in** {channel.mention}')
+
+            await ctx.send(f'\N{WHITE HEAVY CHECK MARK} **Finished deleting** `{c}` **of** {user.mention}**\'s messages**')
+
+        except exc.Abort:
+            await ctx.send('**Deletion aborted**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except TimeoutError:
+            await ctx.send('**Deletion timed out**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @_prune_user.command(name='all', aliases=['a'], brief='Prune a user\'s messages from the guild', description='about flag centers on message 50 of 101 messages\n\npfg \{user id\} [before|after|about] [\{message id\}]\n\nExample:\npfg \{user id\} before \{message id\}', hidden=True)
+    @cmds.is_owner()
+    async def _prune_user_all(self, ctx, user: d.User):
+        def confirm(r, u):
+            if u is ctx.author:
+                if r.emoji == '\N{OCTAGONAL SIGN}':
+                    raise exc.Abort
+                if r.emoji == '\N{THUMBS UP SIGN}':
+                    return True
+            return False
+
+        try:
+            pruning = await ctx.send(f'\N{HOURGLASS} **Pruning** {user.mention}**\'s messages might take some time.** Proceed, {ctx.author.mention}?')
+            await pruning.add_reaction('\N{THUMBS UP SIGN}')
+            await pruning.add_reaction('\N{OCTAGONAL SIGN}')
+            await asyncio.sleep(1)
+
+            await self.bot.wait_for('reaction_add', check=confirm, timeout=10 * 60)
+
+            deleting = await ctx.send(f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages...**')
+            await asyncio.sleep(1)
+
+            c = 0
+            for channel in ctx.guild.text_channels:
+                await deleting.edit(content=f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages from** {channel.mention}')
+
+                deleted = await channel.purge(check=lambda m: m.author.id == user.id, before=pruning, limit=None)
+                c += len(deleted)
+
+            await asyncio.sleep(1)
+
+            for channel in ctx.guild.text_channels:
+                missed = 0
+                async for message in channel.history(before=pruning, limit=None):
+                    if message.author.id == user.id:
+                        missed += 1
+
+                if missed > 0:
+                    await ctx.send(f'\N{DOUBLE EXCLAMATION MARK} `{missed}` **messages were not deleted in** {channel.mention}')
+
+            await ctx.send(f'\N{WHITE HEAVY CHECK MARK} **Finished deleting** `{c}` **of** {user.mention}**\'s messages**')
+
+        except exc.Abort:
+            await ctx.send('**Deletion aborted**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+        except TimeoutError:
+            await ctx.send('**Deletion timed out**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @cmds.group(aliases=['task', 'tsk'])
+    async def tasks(self):
+        pass
+
+    async def delete(self):
+        while self.deleting:
+            message = await self.queue.get()
+            await asyncio.sleep(self.RATE_LIMIT)
+            with suppress(err.NotFound):
+                if not message.pinned:
+                    await message.delete()
+
+        print('STOPPED : deleting')
+
+    async def queue_for_deletion(self, channel):
+        def check(msg):
+            if 'stop d' in msg.content.lower() and msg.channel is channel and msg.author.guild_permissions.administrator:
+                raise exc.Abort
+            elif msg.channel is channel and not msg.pinned:
+                return True
+            return False
+
+        try:
+            async for message in channel.history(limit=None):
+                if 'stop d' in message.content.lower() and message.author.guild_permissions.administrator:
+                    raise exc.Abort
+                if not message.pinned:
+                    await self.queue.put(message)
+
+            while not self.bot.is_closed():
+                message = await self.bot.wait_for('message', check=check)
+                await self.queue.put(message)
+
+        except exc.Abort:
+            u.tasks['auto_del'].remove(channel.id)
+            u.dump(u.tasks, 'cogs/tasks.pkl')
+            if not u.tasks['auto_del']:
+                self.deleting = False
+            print('STOPPED : deleting #{}'.format(channel.name))
+            await channel.send('**Stopped queueing messages for deletion in** {}'.format(channel.mention), delete_after=5)
+
+    @cmds.command(name='autodelete', aliases=['autodel'])
+    @cmds.has_permissions(administrator=True)
+    async def auto_delete(self, ctx):
+        try:
+            if ctx.channel.id not in u.tasks['auto_del']:
+                u.tasks['auto_del'].append(ctx.channel.id)
+                u.dump(u.tasks, 'cogs/tasks.pkl')
+                self.bot.loop.create_task(self.queue_for_deletion(ctx.channel))
+                if not self.deleting:
+                    self.bot.loop.create_task(self.delete())
+                    self.deleting = True
+                print('STARTED : auto-deleting in #{}'.format(ctx.channel.name))
+                await ctx.send('**Auto-deleting all messages in {}**'.format(ctx.channel.mention), delete_after=5)
+            else:
+                raise exc.Exists
+
+        except exc.Exists:
+            await ctx.send('**Already auto-deleting in {}.** Type `stop d(eleting)` to stop.'.format(ctx.channel.mention), delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+    @cmds.group(aliases=['setting', 'set', 's'])
+    @cmds.has_permissions(administrator=True)
+    async def settings(self, ctx):
+        pass
+
+    @settings.command(name='deletecommands', aliases=['delcmds', 'delcmd'])
+    async def _settings_deletecommands(self, ctx):
+        if ctx.guild.id not in u.settings['del_ctx']:
+            u.settings['del_ctx'].append(ctx.guild.id)
+        else:
+            u.settings['del_ctx'].remove(ctx.guild.id)
+        u.dump(u.settings, 'settings.pkl')
+
+        await ctx.send('**Delete command invocations:** `{}`'.format(ctx.guild.id in u.settings['del_ctx']))
+
+    @settings.command(name='prefix', aliases=['pre', 'p'])
+    async def _settings_prefix(self, ctx, *prefixes):
+        if prefixes:
+            u.settings['prefixes'][ctx.guild.id] = prefixes
+        else:
+            with suppress(KeyError):
+                del u.settings['prefixes'][ctx.guild.id]
+
+        await ctx.send(f'**Prefix set to:** `{"` or `".join(prefixes if ctx.guild.id in u.settings["prefixes"] else u.config["prefix"])}`')
+
+    @settings.command(name='deleteresponses', aliases=['delresps', 'delresp'])
+    async def _settings_deleteresponses(self, ctx):
+        if ctx.guild.id not in u.settings['del_resp']:
+            u.settings['del_resp'].append(ctx.guild.id)
+        else:
+            u.settings['del_resp'].remove(ctx.guild.id)
+        u.dump(u.settings, 'settings.pkl')
+
+        await ctx.send(f'**Delete command responses:** `{ctx.guild.id in u.settings["del_resp"]}`')
diff --git a/src/cogs/owner.py b/src/cogs/owner.py
index 03915fd..6c5135f 100644
--- a/src/cogs/owner.py
+++ b/src/cogs/owner.py
@@ -1,245 +1,245 @@
-import asyncio
-import code
-import io
-import os
-import re
-import sys
-import traceback as tb
-from contextlib import redirect_stdout, suppress
-
-import discord as d
-import pyrasite as pyr
-from discord.ext import commands as cmds
-
-from misc import exceptions as exc
-from misc import checks
-from utils import utils as u
-
-
-class Bot:
-
-    def __init__(self, bot):
-        self.bot = bot
-
-    # Close connection to Discord - immediate offline
-    @cmds.command(name=',die', aliases=[',d'], brief='Kills the bot', description='BOT OWNER ONLY\nCloses the connection to Discord', hidden=True)
-    @cmds.is_owner()
-    async def die(self, ctx):
-        await ctx.message.add_reaction('\N{CRESCENT MOON}')
-
-        await self.bot.get_channel(u.config['info_channel']).send('**Shutting down** \N{CRESCENT MOON} . . .')
-
-        chantype = 'guild' if isinstance(ctx.channel, d.TextChannel) else 'private'
-        u.temp['startup'] = (chantype, ctx.channel.id if chantype == 'guild' else ctx.author.id, ctx.message.id)
-        u.dump(u.temp, 'temp/temp.pkl')
-
-        # loop = self.bot.loop.all_tasks()
-        # for task in loop:
-        #     task.cancel()
-        await self.bot.logout()
-        u.close(self.bot.loop)
-        print('\n< < < < < < < < < < < <\nD I S C O N N E C T E D\n< < < < < < < < < < < <\n')
-        # u.notify('D I S C O N N E C T E D')
-
-    @cmds.command(name=',restart', aliases=[',res', ',r'], hidden=True)
-    @cmds.is_owner()
-    async def restart(self, ctx):
-        await ctx.message.add_reaction('\N{SLEEPING SYMBOL}')
-
-        print('\n^ ^ ^ ^ ^ ^ ^ ^ ^ ^\nR E S T A R T I N G\n^ ^ ^ ^ ^ ^ ^ ^ ^ ^\n')
-        await self.bot.get_channel(u.config['info_channel']).send('**Restarting** \N{SLEEPING SYMBOL} . . .')
-        # u.notify('R E S T A R T I N G')
-
-        chantype = 'guild' if isinstance(ctx.channel, d.TextChannel) else 'private'
-        u.temp['startup'] = (chantype, ctx.channel.id if chantype == 'guild' else ctx.author.id, ctx.message.id)
-        u.dump(u.temp, 'temp/temp.pkl')
-
-        # loop = self.bot.loop.all_tasks()
-        # for task in loop:
-        #     task.cancel()
-        await self.bot.logout()
-        u.close(self.bot.loop)
-        os.execl(sys.executable, 'python3', 'run.py')
-
-    # Invite bot to bot owner's server
-    @cmds.command(name=',invite', aliases=[',inv', ',link'], brief='Invite the bot', description='BOT OWNER ONLY\nInvite the bot to a server (Requires admin)', hidden=True)
-    @cmds.is_owner()
-    async def invite(self, ctx):
-        await ctx.message.add_reaction('\N{ENVELOPE}')
-
-        await ctx.send('https://discordapp.com/oauth2/authorize?&client_id={}&scope=bot&permissions={}'.format(u.config['client_id'], u.config['permissions']), delete_after=5)
-
-    @cmds.command(name=',guilds', aliases=[',glds', ',servers', ',servs'])
-    @cmds.is_owner()
-    async def guilds(self, ctx):
-        paginator = cmds.Paginator()
-
-        for guild in self.bot.guilds:
-            paginator.add_line(guild.name)
-
-        for page in paginator.pages:
-            await ctx.send(f'**Guilds:**\n{page}')
-
-    @cmds.command(name=',status', aliases=[',presence', ',game'], hidden=True)
-    @cmds.is_owner()
-    async def change_status(self, ctx, *, game=None):
-        if game:
-            await self.bot.change_presence(game=d.Game(name=game))
-            u.config['playing'] = game
-            u.dump(u.config, 'config.json', json=True)
-            await ctx.send(f'**Game changed to** `{game}`')
-        else:
-            await self.bot.change_presence(game=None)
-            u.config['playing'] = ''
-            u.dump(u.config, 'config.json', json=True)
-            await ctx.send('**Game changed to** ` `')
-
-    @cmds.command(name=',username', aliases=[',user'], hidden=True)
-    @cmds.is_owner()
-    async def change_username(self, ctx, *, username=None):
-        if username:
-            await self.bot.user.edit(username=username)
-            await ctx.send(f'**Username changed to** `{username}`')
-        else:
-            await ctx.send('**Invalid string**', delete_after=7)
-            await ctx.message.add_reaction('\N{CROSS MARK}')
-
-
-class Tools:
-
-    def __init__(self, bot):
-        self.bot = bot
-
-    def format(self, i='', o=''):
-        if len(o) > 1:
-            return '>>> {}\n{}'.format(i, o)
-        else:
-            return '>>> {}'.format(i)
-
-    async def generate(self, d, i='', o=''):
-        return await d.send('```python\n{}```'.format(self.format(i, o)))
-
-    async def refresh(self, m, i='', o=''):
-        output = m.content[9:-3]
-        if len(re.findall('\n', output)) <= 20:
-            await m.edit(content='```python\n{}\n{}\n>>>```'.format(output, self.format(i, o)))
-        else:
-            await m.edit(content='```python\n{}```'.format(self.format(i, o)))
-
-    async def generate_err(self, d, o=''):
-        return await d.send('```\n{}```'.format(o))
-
-    async def refresh_err(self, m, o=''):
-        await m.edit(content='```\n{}```'.format(o))
-
-    @cmds.command(name=',console', aliases=[',con', ',c'], hidden=True)
-    @cmds.is_owner()
-    async def console(self, ctx):
-        def execute(msg):
-            if msg.content.lower().startswith('exec ') and msg.author is ctx.author and msg.channel is ctx.channel:
-                msg.content = msg.content[5:]
-                return True
-            return False
-
-        def evaluate(msg):
-            if msg.content.lower().startswith('eval ') and msg.author is ctx.author and msg.channel is ctx.channel:
-                msg.content = msg.content[5:]
-                return True
-            return False
-
-        def exit(reaction, user):
-            if reaction.emoji == '\N{OCTAGONAL SIGN}' and user is ctx.author and reaction.message.id == ctx.message.id:
-                raise exc.Abort
-            return False
-
-        try:
-            console = await self.generate(ctx)
-            exception = await self.generate_err(ctx)
-
-            await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
-
-            while not self.bot.is_closed():
-                try:
-                    done, pending = await asyncio.wait([self.bot.wait_for('message', check=execute), self.bot.wait_for('message', check=evaluate), self.bot.wait_for('reaction_add', check=exit)], return_when=asyncio.FIRST_COMPLETED)
-
-                    message = done.pop().result()
-                    print(message.content)
-
-                except exc.Execute:
-                    try:
-                        sys.stdout = io.StringIO()
-                        sys.stderr = io.StringIO()
-                        exec(message.content)
-
-                    except Exception:
-                        await self.refresh_err(exception, tb.format_exc(limit=1))
-
-                    finally:
-                        await self.refresh(console, message.content, sys.stdout.getvalue() if sys.stdout.getvalue() != console.content else None)
-                        sys.stdout = sys.__stdout__
-                        sys.stderr = sys.__stderr__
-                        with suppress(d.NotFound):
-                            await message.delete()
-
-                except exc.Evaluate:
-                    try:
-                        sys.stdout = io.StringIO()
-                        sys.stderr = io.StringIO()
-                        eval(message.content)
-
-                    except Exception:
-                        await self.refresh_err(exception, tb.format_exc(limit=1))
-
-                    finally:
-                        await self.refresh(console, message.content, sys.stdout.getvalue() if sys.stdout.getvalue() != console.content else None)
-                        sys.stdout = sys.__stdout__
-                        sys.stderr = sys.__stderr__
-                        with suppress(d.NotFound):
-                            await message.delete()
-
-        except exc.Abort:
-            pass
-
-        finally:
-            sys.stdout = sys.__stdout__
-            sys.stderr = sys.__stderr__
-            print('RESET : sys.std output/error')
-
-    @cmds.command(name=',execute', aliases=[',exec'], hidden=True)
-    @cmds.is_owner()
-    async def execute(self, ctx, *, exe):
-        try:
-            with io.StringIO() as buff, redirect_stdout(buff):
-                exec(exe)
-                await self.generate(ctx, exe, f'\n{buff.getvalue()}')
-
-        except Exception:
-            await self.generate(ctx, exe, f'\n{tb.format_exc()}')
-
-    @cmds.command(name=',evaluate', aliases=[',eval'], hidden=True)
-    @cmds.is_owner()
-    async def evaluate(self, ctx, *, evl):
-        try:
-            with io.StringIO() as buff, redirect_stdout(buff):
-                eval(evl)
-                await self.generate(ctx, evl, f'\n{buff.getvalue()}')
-
-        except Exception:
-            await self.generate(ctx, evl, f'\n{tb.format_exc()}')
-
-    @cmds.group(aliases=[',db'], hidden=True)
-    @cmds.is_owner()
-    async def debug(self, ctx):
-        console = await self.generate(ctx)
-
-    @debug.command(name='inject', aliases=['inj'])
-    async def _inject(self, ctx, *, input_):
-        pass
-
-    @debug.command(name='inspect', aliases=['ins'])
-    async def _inspect(self, ctx, *, input_):
-        pass
-
-    # @cmds.command(name='endpoint', aliases=['end'])
-    # async def get_endpoint(self, ctx, *args):
-    #     await ctx.send(f'```\n{await u.fetch(f"https://{args[0]}/{args[1]}/{args[2]}", params={args[3]: args[4], "limit": 1}, json=True)}```')
+import asyncio
+import code
+import io
+import os
+import re
+import sys
+import traceback as tb
+from contextlib import redirect_stdout, suppress
+
+import discord as d
+import pyrasite as pyr
+from discord.ext import commands as cmds
+
+from misc import exceptions as exc
+from misc import checks
+from utils import utils as u
+
+
+class Bot:
+
+    def __init__(self, bot):
+        self.bot = bot
+
+    # Close connection to Discord - immediate offline
+    @cmds.command(name=',die', aliases=[',d'], brief='Kills the bot', description='BOT OWNER ONLY\nCloses the connection to Discord', hidden=True)
+    @cmds.is_owner()
+    async def die(self, ctx):
+        await ctx.message.add_reaction('\N{CRESCENT MOON}')
+
+        await self.bot.get_channel(u.config['info_channel']).send('**Shutting down** \N{CRESCENT MOON} . . .')
+
+        chantype = 'guild' if isinstance(ctx.channel, d.TextChannel) else 'private'
+        u.temp['startup'] = (chantype, ctx.channel.id if chantype == 'guild' else ctx.author.id, ctx.message.id)
+        u.dump(u.temp, 'temp/temp.pkl')
+
+        # loop = self.bot.loop.all_tasks()
+        # for task in loop:
+        #     task.cancel()
+        await self.bot.logout()
+        u.close(self.bot.loop)
+        print('\n< < < < < < < < < < < <\nD I S C O N N E C T E D\n< < < < < < < < < < < <\n')
+        # u.notify('D I S C O N N E C T E D')
+
+    @cmds.command(name=',restart', aliases=[',res', ',r'], hidden=True)
+    @cmds.is_owner()
+    async def restart(self, ctx):
+        await ctx.message.add_reaction('\N{SLEEPING SYMBOL}')
+
+        print('\n^ ^ ^ ^ ^ ^ ^ ^ ^ ^\nR E S T A R T I N G\n^ ^ ^ ^ ^ ^ ^ ^ ^ ^\n')
+        await self.bot.get_channel(u.config['info_channel']).send('**Restarting** \N{SLEEPING SYMBOL} . . .')
+        # u.notify('R E S T A R T I N G')
+
+        chantype = 'guild' if isinstance(ctx.channel, d.TextChannel) else 'private'
+        u.temp['startup'] = (chantype, ctx.channel.id if chantype == 'guild' else ctx.author.id, ctx.message.id)
+        u.dump(u.temp, 'temp/temp.pkl')
+
+        # loop = self.bot.loop.all_tasks()
+        # for task in loop:
+        #     task.cancel()
+        await self.bot.logout()
+        u.close(self.bot.loop)
+        os.execl(sys.executable, 'python3', 'run.py')
+
+    # Invite bot to bot owner's server
+    @cmds.command(name=',invite', aliases=[',inv', ',link'], brief='Invite the bot', description='BOT OWNER ONLY\nInvite the bot to a server (Requires admin)', hidden=True)
+    @cmds.is_owner()
+    async def invite(self, ctx):
+        await ctx.message.add_reaction('\N{ENVELOPE}')
+
+        await ctx.send('https://discordapp.com/oauth2/authorize?&client_id={}&scope=bot&permissions={}'.format(u.config['client_id'], u.config['permissions']), delete_after=5)
+
+    @cmds.command(name=',guilds', aliases=[',glds', ',servers', ',servs'])
+    @cmds.is_owner()
+    async def guilds(self, ctx):
+        paginator = cmds.Paginator()
+
+        for guild in self.bot.guilds:
+            paginator.add_line(guild.name)
+
+        for page in paginator.pages:
+            await ctx.send(f'**Guilds:**\n{page}')
+
+    @cmds.command(name=',status', aliases=[',presence', ',game'], hidden=True)
+    @cmds.is_owner()
+    async def change_status(self, ctx, *, game=None):
+        if game:
+            await self.bot.change_presence(game=d.Game(name=game))
+            u.config['playing'] = game
+            u.dump(u.config, 'config.json', json=True)
+            await ctx.send(f'**Game changed to** `{game}`')
+        else:
+            await self.bot.change_presence(game=None)
+            u.config['playing'] = ''
+            u.dump(u.config, 'config.json', json=True)
+            await ctx.send('**Game changed to** ` `')
+
+    @cmds.command(name=',username', aliases=[',user'], hidden=True)
+    @cmds.is_owner()
+    async def change_username(self, ctx, *, username=None):
+        if username:
+            await self.bot.user.edit(username=username)
+            await ctx.send(f'**Username changed to** `{username}`')
+        else:
+            await ctx.send('**Invalid string**', delete_after=7)
+            await ctx.message.add_reaction('\N{CROSS MARK}')
+
+
+class Tools:
+
+    def __init__(self, bot):
+        self.bot = bot
+
+    def format(self, i='', o=''):
+        if len(o) > 1:
+            return '>>> {}\n{}'.format(i, o)
+        else:
+            return '>>> {}'.format(i)
+
+    async def generate(self, d, i='', o=''):
+        return await d.send('```python\n{}```'.format(self.format(i, o)))
+
+    async def refresh(self, m, i='', o=''):
+        output = m.content[9:-3]
+        if len(re.findall('\n', output)) <= 20:
+            await m.edit(content='```python\n{}\n{}\n>>>```'.format(output, self.format(i, o)))
+        else:
+            await m.edit(content='```python\n{}```'.format(self.format(i, o)))
+
+    async def generate_err(self, d, o=''):
+        return await d.send('```\n{}```'.format(o))
+
+    async def refresh_err(self, m, o=''):
+        await m.edit(content='```\n{}```'.format(o))
+
+    @cmds.command(name=',console', aliases=[',con', ',c'], hidden=True)
+    @cmds.is_owner()
+    async def console(self, ctx):
+        def execute(msg):
+            if msg.content.lower().startswith('exec ') and msg.author is ctx.author and msg.channel is ctx.channel:
+                msg.content = msg.content[5:]
+                return True
+            return False
+
+        def evaluate(msg):
+            if msg.content.lower().startswith('eval ') and msg.author is ctx.author and msg.channel is ctx.channel:
+                msg.content = msg.content[5:]
+                return True
+            return False
+
+        def exit(reaction, user):
+            if reaction.emoji == '\N{OCTAGONAL SIGN}' and user is ctx.author and reaction.message.id == ctx.message.id:
+                raise exc.Abort
+            return False
+
+        try:
+            console = await self.generate(ctx)
+            exception = await self.generate_err(ctx)
+
+            await ctx.message.add_reaction('\N{OCTAGONAL SIGN}')
+
+            while not self.bot.is_closed():
+                try:
+                    done, pending = await asyncio.wait([self.bot.wait_for('message', check=execute), self.bot.wait_for('message', check=evaluate), self.bot.wait_for('reaction_add', check=exit)], return_when=asyncio.FIRST_COMPLETED)
+
+                    message = done.pop().result()
+                    print(message.content)
+
+                except exc.Execute:
+                    try:
+                        sys.stdout = io.StringIO()
+                        sys.stderr = io.StringIO()
+                        exec(message.content)
+
+                    except Exception:
+                        await self.refresh_err(exception, tb.format_exc(limit=1))
+
+                    finally:
+                        await self.refresh(console, message.content, sys.stdout.getvalue() if sys.stdout.getvalue() != console.content else None)
+                        sys.stdout = sys.__stdout__
+                        sys.stderr = sys.__stderr__
+                        with suppress(d.NotFound):
+                            await message.delete()
+
+                except exc.Evaluate:
+                    try:
+                        sys.stdout = io.StringIO()
+                        sys.stderr = io.StringIO()
+                        eval(message.content)
+
+                    except Exception:
+                        await self.refresh_err(exception, tb.format_exc(limit=1))
+
+                    finally:
+                        await self.refresh(console, message.content, sys.stdout.getvalue() if sys.stdout.getvalue() != console.content else None)
+                        sys.stdout = sys.__stdout__
+                        sys.stderr = sys.__stderr__
+                        with suppress(d.NotFound):
+                            await message.delete()
+
+        except exc.Abort:
+            pass
+
+        finally:
+            sys.stdout = sys.__stdout__
+            sys.stderr = sys.__stderr__
+            print('RESET : sys.std output/error')
+
+    @cmds.command(name=',execute', aliases=[',exec'], hidden=True)
+    @cmds.is_owner()
+    async def execute(self, ctx, *, exe):
+        try:
+            with io.StringIO() as buff, redirect_stdout(buff):
+                exec(exe)
+                await self.generate(ctx, exe, f'\n{buff.getvalue()}')
+
+        except Exception:
+            await self.generate(ctx, exe, f'\n{tb.format_exc()}')
+
+    @cmds.command(name=',evaluate', aliases=[',eval'], hidden=True)
+    @cmds.is_owner()
+    async def evaluate(self, ctx, *, evl):
+        try:
+            with io.StringIO() as buff, redirect_stdout(buff):
+                eval(evl)
+                await self.generate(ctx, evl, f'\n{buff.getvalue()}')
+
+        except Exception:
+            await self.generate(ctx, evl, f'\n{tb.format_exc()}')
+
+    @cmds.group(aliases=[',db'], hidden=True)
+    @cmds.is_owner()
+    async def debug(self, ctx):
+        console = await self.generate(ctx)
+
+    @debug.command(name='inject', aliases=['inj'])
+    async def _inject(self, ctx, *, input_):
+        pass
+
+    @debug.command(name='inspect', aliases=['ins'])
+    async def _inspect(self, ctx, *, input_):
+        pass
+
+    # @cmds.command(name='endpoint', aliases=['end'])
+    # async def get_endpoint(self, ctx, *args):
+    #     await ctx.send(f'```\n{await u.fetch(f"https://{args[0]}/{args[1]}/{args[2]}", params={args[3]: args[4], "limit": 1}, json=True)}```')
diff --git a/src/utils/utils.py b/src/utils/utils.py
index a2d8f92..f7236f1 100644
--- a/src/utils/utils.py
+++ b/src/utils/utils.py
@@ -1,191 +1,191 @@
-import asyncio
-import json as jsn
-import os
-import pickle as pkl
-import subprocess
-from contextlib import suppress
-from fractions import gcd
-import math
-import gmusicapi as gpm
-
-import aiohttp
-import discord as d
-
-from misc import exceptions as exc
-
-# from pync import Notifier
-
-
-print('\nPID : {}\n'.format(os.getpid()))
-
-
-# def notify(message):
-#     subprocess.run(['terminal-notifier', '-message', message, '-title',
-#                     'Modumind', '-activate', 'com.apple.Terminal', '-appIcon', 'icon.png', '-sound', 'Ping'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-
-
-try:
-    with open('config.json') as infile:
-        config = jsn.load(infile)
-        print('LOADED : config.json')
-
-except FileNotFoundError:
-    with open('config.json', 'w') as outfile:
-        jsn.dump({'client_id': 0, 'info_channel': 0, 'owner_id': 0, 'permissions': 126016,
-                  'playing': 'a game', 'prefix': [',', 'm,'], 'selfbot': False, 'token': 'str'}, outfile, indent=4, sort_keys=True)
-        print('FILE NOT FOUND : config.json created with abstract values. Restart run.py with correct values')
-
-
-def setdefault(filename, default=None, json=False):
-    if json:
-        try:
-            with open(filename, 'r') as infile:
-                print(f'LOADED : {filename}')
-                return jsn.load(infile)
-
-        except FileNotFoundError:
-            with open(filename, 'w+') as iofile:
-                print(f'FILE NOT FOUND : {filename} created and loaded with default values')
-                jsn.dump(default, iofile)
-                iofile.seek(0)
-                return jsn.load(iofile)
-    else:
-        try:
-            with open(filename, 'rb') as infile:
-                print(f'LOADED : {filename}')
-                return pkl.load(infile)
-
-        except FileNotFoundError:
-            with open(filename, 'wb+') as iofile:
-                print(f'FILE NOT FOUND : {filename} created and loaded with default values')
-                pkl.dump(default, iofile)
-                iofile.seek(0)
-                return pkl.load(iofile)
-
-
-def load(filename, *, json=False):
-    if not json:
-        with open(filename, 'rb') as infile:
-            return pkl.load(infile)
-    else:
-        with open(filename) as infile:
-            return jsn.load(infile)
-
-
-def dump(obj, filename, *, json=False):
-    if not json:
-        with open(filename, 'wb') as outfile:
-            pkl.dump(obj, outfile)
-    else:
-        with open(filename, 'w') as outfile:
-            jsn.dump(obj, outfile, indent=4, sort_keys=True)
-
-
-settings = setdefault('misc/settings.pkl', default={'del_ctx': [], 'del_resp': [], 'prefixes': {}})
-tasks = setdefault('cogs/tasks.pkl', default={'auto_del': [], 'auto_hrt': [], 'auto_rev': [], 'periodic_gpm': []})
-temp = setdefault('temp/temp.pkl', default={'startup': ()})
-secrets = setdefault('secrets.json', default={'client_secrets': {'client_id': '', 'client_secret': ''}}, json=True)
-
-RATE_LIMIT = 2.2
-color = d.Color(0x1A1A1A)
-session = aiohttp.ClientSession()
-last_commands = {}
-
-
-async def fetch(url, *, params={}, json=False, response=False):
-    async with session.get(url, params=params, headers={'User-Agent': 'Myned/Modufur'}) as r:
-        if response:
-            return r
-        elif json:
-            return await r.json()
-        return await r.read()
-
-
-# async def clear(obj, interval=10 * 60, replace=None):
-#     if replace is None:
-#         if type(obj) is list:
-#             replace = []
-#         elif type(obj) is dict:
-#             replace = {}
-#         elif type(obj) is int:
-#             replace = 0
-#         elif type(obj) is str:
-#             replace = ''
-#
-#     while True:
-#         obj = replace
-#         asyncio.sleep(interval)
-
-
-def close(loop):
-    if session:
-        session.close()
-
-    loop.stop()
-    pending = asyncio.Task.all_tasks()
-    for task in pending:
-        task.cancel()
-        # with suppress(asyncio.CancelledError):
-        #     loop.run_until_complete(task)
-    # loop.close()
-
-    print('Finished cancelling tasks.')
-
-
-def generate_embed(ctx, *, title=d.Embed.Empty, kind='rich', description=d.Embed.Empty, url=d.Embed.Empty, timestamp=d.Embed.Empty, colour=color, footer={}, image=d.Embed.Empty, thumbnail=d.Embed.Empty, author={}, fields=[]):
-    embed = d.Embed(title=title, type=kind, description=description, url=url, timestamp=timestamp, colour=colour if isinstance(ctx.channel, d.TextChannel) else color)
-
-    if footer:
-        embed.set_footer(text=footer.get('text', d.Embed.Empty), icon_url=footer.get('icon_url', d.Embed.Empty))
-    if image:
-        embed.set_image(url=image)
-    if thumbnail:
-        embed.set_thumbnail(url=thumbnail)
-    if author:
-        embed.set_author(name=author.get('name', d.Embed.Empty), url=author.get('url', d.Embed.Empty), icon_url=author.get('icon_url', d.Embed.Empty))
-    for field in fields:
-        embed.add_field(name=field.get('name', d.Embed.Empty), value=field.get('value', d.Embed.Empty), inline=field.get('inline', True))
-
-    return embed
-
-def get_kwargs(ctx, args, *, limit=False):
-    destination = ctx
-    remaining = list(args[:])
-    rm = False
-    lim = 1
-
-    for flag in ('-d', '-dm'):
-        if flag in remaining:
-            destination = ctx.author
-
-            remaining.remove(flag)
-
-    for flag in ('-r', '-rm', '-remove', '-re', '-repl', '-replace'):
-        if flag in remaining and ctx.author.permissions_in(ctx.channel).manage_messages:
-            rm = True
-
-            remaining.remove(flag)
-
-    if limit:
-        for arg in remaining:
-            if arg.isdigit():
-                if 1 <= int(arg) <= limit:
-                    lim = int(arg)
-                    remaining.remove(arg)
-                    break
-                else:
-                    raise exc.BoundsError(arg)
-
-    return {'destination': destination, 'remaining': remaining, 'remove': rm, 'limit': lim}
-
-
-def get_aspectratio(a, b):
-    divisor = gcd(a, b)
-    return f'{int(a / divisor)}:{int(b / divisor)}'
-
-
-def ci(pos, n):
-    z = 1.96
-    phat = float(pos) / n
-
-    return (phat + z*z/(2*n) - z * math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)
+import asyncio
+import json as jsn
+import os
+import pickle as pkl
+import subprocess
+from contextlib import suppress
+from fractions import gcd
+import math
+import gmusicapi as gpm
+
+import aiohttp
+import discord as d
+
+from misc import exceptions as exc
+
+# from pync import Notifier
+
+
+print('\nPID : {}\n'.format(os.getpid()))
+
+
+# def notify(message):
+#     subprocess.run(['terminal-notifier', '-message', message, '-title',
+#                     'Modumind', '-activate', 'com.apple.Terminal', '-appIcon', 'icon.png', '-sound', 'Ping'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+
+try:
+    with open('config.json') as infile:
+        config = jsn.load(infile)
+        print('LOADED : config.json')
+
+except FileNotFoundError:
+    with open('config.json', 'w') as outfile:
+        jsn.dump({'client_id': 0, 'info_channel': 0, 'owner_id': 0, 'permissions': 126016,
+                  'playing': 'a game', 'prefix': [',', 'm,'], 'selfbot': False, 'token': 'str'}, outfile, indent=4, sort_keys=True)
+        print('FILE NOT FOUND : config.json created with abstract values. Restart run.py with correct values')
+
+
+def setdefault(filename, default=None, json=False):
+    if json:
+        try:
+            with open(filename, 'r') as infile:
+                print(f'LOADED : {filename}')
+                return jsn.load(infile)
+
+        except FileNotFoundError:
+            with open(filename, 'w+') as iofile:
+                print(f'FILE NOT FOUND : {filename} created and loaded with default values')
+                jsn.dump(default, iofile)
+                iofile.seek(0)
+                return jsn.load(iofile)
+    else:
+        try:
+            with open(filename, 'rb') as infile:
+                print(f'LOADED : {filename}')
+                return pkl.load(infile)
+
+        except FileNotFoundError:
+            with open(filename, 'wb+') as iofile:
+                print(f'FILE NOT FOUND : {filename} created and loaded with default values')
+                pkl.dump(default, iofile)
+                iofile.seek(0)
+                return pkl.load(iofile)
+
+
+def load(filename, *, json=False):
+    if not json:
+        with open(filename, 'rb') as infile:
+            return pkl.load(infile)
+    else:
+        with open(filename) as infile:
+            return jsn.load(infile)
+
+
+def dump(obj, filename, *, json=False):
+    if not json:
+        with open(filename, 'wb') as outfile:
+            pkl.dump(obj, outfile)
+    else:
+        with open(filename, 'w') as outfile:
+            jsn.dump(obj, outfile, indent=4, sort_keys=True)
+
+
+settings = setdefault('misc/settings.pkl', default={'del_ctx': [], 'del_resp': [], 'prefixes': {}})
+tasks = setdefault('cogs/tasks.pkl', default={'auto_del': [], 'auto_hrt': [], 'auto_rev': [], 'periodic_gpm': []})
+temp = setdefault('temp/temp.pkl', default={'startup': ()})
+secrets = setdefault('secrets.json', default={'client_secrets': {'client_id': '', 'client_secret': ''}}, json=True)
+
+RATE_LIMIT = 2.2
+color = d.Color(0x1A1A1A)
+session = aiohttp.ClientSession()
+last_commands = {}
+
+
+async def fetch(url, *, params={}, json=False, response=False):
+    async with session.get(url, params=params, headers={'User-Agent': 'Myned/Modufur'}) as r:
+        if response:
+            return r
+        elif json:
+            return await r.json()
+        return await r.read()
+
+
+# async def clear(obj, interval=10 * 60, replace=None):
+#     if replace is None:
+#         if type(obj) is list:
+#             replace = []
+#         elif type(obj) is dict:
+#             replace = {}
+#         elif type(obj) is int:
+#             replace = 0
+#         elif type(obj) is str:
+#             replace = ''
+#
+#     while True:
+#         obj = replace
+#         asyncio.sleep(interval)
+
+
+def close(loop):
+    if session:
+        session.close()
+
+    loop.stop()
+    pending = asyncio.Task.all_tasks()
+    for task in pending:
+        task.cancel()
+        # with suppress(asyncio.CancelledError):
+        #     loop.run_until_complete(task)
+    # loop.close()
+
+    print('Finished cancelling tasks.')
+
+
+def generate_embed(ctx, *, title=d.Embed.Empty, kind='rich', description=d.Embed.Empty, url=d.Embed.Empty, timestamp=d.Embed.Empty, colour=color, footer={}, image=d.Embed.Empty, thumbnail=d.Embed.Empty, author={}, fields=[]):
+    embed = d.Embed(title=title, type=kind, description=description, url=url, timestamp=timestamp, colour=colour if isinstance(ctx.channel, d.TextChannel) else color)
+
+    if footer:
+        embed.set_footer(text=footer.get('text', d.Embed.Empty), icon_url=footer.get('icon_url', d.Embed.Empty))
+    if image:
+        embed.set_image(url=image)
+    if thumbnail:
+        embed.set_thumbnail(url=thumbnail)
+    if author:
+        embed.set_author(name=author.get('name', d.Embed.Empty), url=author.get('url', d.Embed.Empty), icon_url=author.get('icon_url', d.Embed.Empty))
+    for field in fields:
+        embed.add_field(name=field.get('name', d.Embed.Empty), value=field.get('value', d.Embed.Empty), inline=field.get('inline', True))
+
+    return embed
+
+def get_kwargs(ctx, args, *, limit=False):
+    destination = ctx
+    remaining = list(args[:])
+    rm = False
+    lim = 1
+
+    for flag in ('-d', '-dm'):
+        if flag in remaining:
+            destination = ctx.author
+
+            remaining.remove(flag)
+
+    for flag in ('-r', '-rm', '-remove', '-re', '-repl', '-replace'):
+        if flag in remaining and ctx.author.permissions_in(ctx.channel).manage_messages:
+            rm = True
+
+            remaining.remove(flag)
+
+    if limit:
+        for arg in remaining:
+            if arg.isdigit():
+                if 1 <= int(arg) <= limit:
+                    lim = int(arg)
+                    remaining.remove(arg)
+                    break
+                else:
+                    raise exc.BoundsError(arg)
+
+    return {'destination': destination, 'remaining': remaining, 'remove': rm, 'limit': lim}
+
+
+def get_aspectratio(a, b):
+    divisor = gcd(a, b)
+    return f'{int(a / divisor)}:{int(b / divisor)}'
+
+
+def ci(pos, n):
+    z = 1.96
+    phat = float(pos) / n
+
+    return (phat + z*z/(2*n) - z * math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)