From d80729c94e85a2259e12befea264ff47d7dc4f61 Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 01:23:43 -0500
Subject: [PATCH 01/13] Add batch files to gitignore

---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index 442a16f..bfc80a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
 *.DS_Store
 *.pkl
 *.png
+*.bat
 
 # Byte-compiled / optimized / DLL files
 __pycache__/

From 27c134644874193e1fa97ae6c510aa1e68e1c4ec Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 01:24:17 -0500
Subject: [PATCH 02/13] Shorten FileNotFound warning output

---
 src/utils/utils.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/utils/utils.py b/src/utils/utils.py
index 5d3e33d..5c83718 100644
--- a/src/utils/utils.py
+++ b/src/utils/utils.py
@@ -33,8 +33,7 @@ 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)
-        raise FileNotFoundError(
-            'FILE NOT FOUND : config.json created with abstract values. Restart run.py with correct values')
+        print('FILE NOT FOUND : config.json created with abstract values. Restart run.py with correct values')
 
 
 def setdefault(filename, default=None, json=False):

From 76d0d348485291672cfdcb0255a3c96dfb6bbf6d Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 01:24:42 -0500
Subject: [PATCH 03/13] Change Modumind user agent to Modufur

---
 src/utils/utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/utils/utils.py b/src/utils/utils.py
index 5c83718..a2d8f92 100644
--- a/src/utils/utils.py
+++ b/src/utils/utils.py
@@ -93,7 +93,7 @@ last_commands = {}
 
 
 async def fetch(url, *, params={}, json=False, response=False):
-    async with session.get(url, params=params, headers={'User-Agent': 'Myned/Modumind'}) as r:
+    async with session.get(url, params=params, headers={'User-Agent': 'Myned/Modufur'}) as r:
         if response:
             return r
         elif json:

From ba9f5f3ac27e5381c93c34a4ab78dc804b7c07ac Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 01:25:18 -0500
Subject: [PATCH 04/13] Add ping-like test command

---
 src/run.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/run.py b/src/run.py
index 2f10f4f..6c8f9ea 100644
--- a/src/run.py
+++ b/src/run.py
@@ -62,7 +62,7 @@ bot = cmds.Bot(command_prefix=get_prefix, self_bot=u.config['selfbot'], formatte
 
 @bot.command(help='help', brief='brief', description='description', usage='usage', hidden=True)
 async def test(ctx):
-    pass
+    await ctx.send('test')
 
 # Send and print ready message to #testing and console after logon
 

From b433ea6273a24c5ea937ceba230aeedb0eaaa6cc Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 01:25:49 -0500
Subject: [PATCH 05/13] Change delete_after to 7 secs in on_command_error
 output

---
 src/run.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/run.py b/src/run.py
index 6c8f9ea..45de512 100644
--- a/src/run.py
+++ b/src/run.py
@@ -144,7 +144,7 @@ async def on_command_error(ctx, error):
     if isinstance(error, err.NotFound):
         print('NOT FOUND')
     elif isinstance(error, errext.CheckFailure):
-        await ctx.send('**Insufficient permissions**', delete_after=10)
+        await ctx.send('**Insufficient permissions**', delete_after=7)
         await ctx.message.add_reaction('\N{NO ENTRY}')
     elif isinstance(error, errext.CommandNotFound):
         print('INVALID COMMAND : {}'.format(error), file=sys.stderr)

From 1c1ecf9d6b0516f66dd5715c95f9a84acea51b7b Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 01:26:13 -0500
Subject: [PATCH 06/13] Add MissingRequiredArgument and BadArgument to
 on_command_error

---
 src/run.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/run.py b/src/run.py
index 45de512..1d72368 100644
--- a/src/run.py
+++ b/src/run.py
@@ -143,6 +143,12 @@ async def on_error(error, *args, **kwargs):
 async def on_command_error(ctx, error):
     if isinstance(error, err.NotFound):
         print('NOT FOUND')
+    elif isinstance(error, errext.MissingRequiredArgument):
+        await ctx.send('**Missing required argument**', delete_after=7)
+        await ctx.message.add_reaction('\N{CROSS MARK}')
+    elif isinstance(error, errext.BadArgument):
+        await ctx.send(f'**Invalid argument.** {error}', delete_after=7)
+        await ctx.message.add_reaction('\N{CROSS MARK}')
     elif isinstance(error, errext.CheckFailure):
         await ctx.send('**Insufficient permissions**', delete_after=7)
         await ctx.message.add_reaction('\N{NO ENTRY}')

From e069023dc6f69fc8363bfea2af76c35006e6c7c5 Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 01:26:46 -0500
Subject: [PATCH 07/13] Import ext.commands errors

---
 src/cogs/management.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/cogs/management.py b/src/cogs/management.py
index 3bd4130..55cb058 100644
--- a/src/cogs/management.py
+++ b/src/cogs/management.py
@@ -6,6 +6,7 @@ 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 09c09199bdddbffa67786addaaf25b2134e3579e Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 01:27:09 -0500
Subject: [PATCH 08/13] Add "purge|pur" aliases to prune group command

---
 src/cogs/management.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/cogs/management.py b/src/cogs/management.py
index 55cb058..97aa933 100644
--- a/src/cogs/management.py
+++ b/src/cogs/management.py
@@ -29,7 +29,7 @@ class Administration:
             self.deleting = True
             self.bot.loop.create_task(self.delete())
 
-    @cmds.group(aliases=['pru', 'clear', 'cl'], hidden=True)
+    @cmds.group(aliases=['pru', 'purge', 'pur', 'clear', 'cl'], hidden=True)
     @cmds.is_owner()
     async def prune(self, ctx):
         pass

From e2bc1e650b6374e2e869aee2ff438121ad438a4b Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 01:28:27 -0500
Subject: [PATCH 09/13] Add purge user from channel command

---
 src/cogs/management.py | 51 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/src/cogs/management.py b/src/cogs/management.py
index 97aa933..bbcdd55 100644
--- a/src/cogs/management.py
+++ b/src/cogs/management.py
@@ -38,6 +38,57 @@ class Administration:
     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, when=None, reference=None):

From 3bd77dea32a777c8add3adcbb5630823567d1e92 Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 01:29:06 -0500
Subject: [PATCH 10/13] Fix mistaken group command decorator

---
 src/cogs/booru.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/cogs/booru.py b/src/cogs/booru.py
index 2be7247..42009b9 100644
--- a/src/cogs/booru.py
+++ b/src/cogs/booru.py
@@ -1195,7 +1195,7 @@ class MsG:
                     n += 1
 
     # Searches for and returns images from e621.net given tags when not blacklisted
-    @cmds.group(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])')
+    @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:

From 1be05458157cc246fd7ffbceb7fc49ba67c2c8bb Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 02:16:27 -0500
Subject: [PATCH 11/13] Change output formatting to align with program-wide
 rules

---
 src/cogs/owner.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/cogs/owner.py b/src/cogs/owner.py
index fd0b560..0d7542e 100644
--- a/src/cogs/owner.py
+++ b/src/cogs/owner.py
@@ -192,7 +192,7 @@ class Tools:
         finally:
             sys.stdout = sys.__stdout__
             sys.stderr = sys.__stderr__
-            print('Reset sys output.')
+            print('RESET : sys.std output/error')
 
     @cmds.command(name=',execute', aliases=[',exec'], hidden=True)
     @cmds.is_owner()

From 0581ab8f5987a695d46b60de732a1940a933e09e Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 02:16:46 -0500
Subject: [PATCH 12/13] Add command to list all guilds bot is in

---
 src/cogs/owner.py | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/cogs/owner.py b/src/cogs/owner.py
index 0d7542e..03915fd 100644
--- a/src/cogs/owner.py
+++ b/src/cogs/owner.py
@@ -69,6 +69,17 @@ class Bot:
 
         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):

From e0f58d9ce4e3821621e803c51dbe963126e1780a Mon Sep 17 00:00:00 2001
From: Myned <alphasea14@gmail.com>
Date: Sun, 14 Jan 2018 02:17:34 -0500
Subject: [PATCH 13/13] Clean prune user all command

---
 src/cogs/management.py | 98 +++++++++++++++---------------------------
 1 file changed, 34 insertions(+), 64 deletions(-)

diff --git a/src/cogs/management.py b/src/cogs/management.py
index bbcdd55..7d6b2f2 100644
--- a/src/cogs/management.py
+++ b/src/cogs/management.py
@@ -91,79 +91,49 @@ class Administration:
 
     @_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, when=None, reference=None):
-        def yes(msg):
-            if msg.content.lower() == 'y' and msg.channel is ctx.channel and msg.author is ctx.author:
-                return True
-            elif msg.content.lower() == 'n' and msg.channel is ctx.channel and msg.author is ctx.author:
-                raise exc.CheckFail
-            else:
-                return False
+    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
 
-        channels = ctx.guild.text_channels
-        if reference is not None:
-            for channel in channels:
-                try:
-                    ref = await channel.get_message(reference)
-
-                except err.NotFound:
-                    continue
-
-        history = []
         try:
-            pru_sent = await ctx.send('\N{HOURGLASS} **Pruning** <@{}>**\'s messages will take some time**'.format(user))
-            ch_sent = await ctx.send('\N{FILE CABINET} **Caching channels...**')
+            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)
 
-            if when is None:
-                for channel in channels:
-                    async for message in channel.history(limit=None):
-                        if message.author.id == int(user):
-                            history.append(message)
-                    await ch_sent.edit(content='\N{FILE CABINET} **Cached** `{}/{}` **channels**'.format(channels.index(channel) + 1, len(channels)))
-                    await asyncio.sleep(self.RATE_LIMIT)
-            elif when == 'before':
-                for channel in channels:
-                    async for message in channel.history(limit=None, before=ref.created_at):
-                        if message.author.id == int(user):
-                            history.append(message)
-                    await ch_sent.edit(content='\N{FILE CABINET} **Cached** `{}/{}` **channels**'.format(channels.index(channel) + 1, len(channels)))
-                    await asyncio.sleep(self.RATE_LIMIT)
-            elif when == 'after':
-                for channel in channels:
-                    async for message in channel.history(limit=None, after=ref.created_at):
-                        if message.author.id == int(user):
-                            history.append(message)
-                    await ch_sent.edit(content='\N{FILE CABINET} **Cached** `{}/{}` **channels**'.format(channels.index(channel) + 1, len(channels)))
-                    await asyncio.sleep(self.RATE_LIMIT)
-            elif when == 'about':
-                for channel in channels:
-                    async for message in channel.history(limit=None, about=ref.created_at):
-                        if message.author.id == int(user):
-                            history.append(message)
-                    await ch_sent.edit(content='\N{FILE CABINET} **Cached** `{}/{}` **channels**'.format(channels.index(channel) + 1, len(channels)))
-                    await asyncio.sleep(self.RATE_LIMIT)
+            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)
 
-            est_sent = await ctx.send('\N{STOPWATCH} **Estimated time to delete history:** `{}m {}s`'.format(int(self.RATE_LIMIT * len(history) / 60), int(self.RATE_LIMIT * len(history) % 60)))
-            cont_sent = await ctx.send('{} **Continue?** `Y` or `N`'.format(ctx.author.mention))
-            await self.bot.wait_for('message', check=yes, timeout=10 * 60)
-            await cont_sent.delete()
-            del_sent = await ctx.send('\N{WASTEBASKET} **Deleting messages..**')
-            await del_sent.pin()
             c = 0
-            for message in history:
-                with suppress(err.NotFound):
-                    await message.delete()
-                    c += 1
-                await del_sent.edit(content='\N{WASTEBASKET} **Deleted** `{}/{}` **messages**'.format(history.index(message) + 1, len(history)))
-                await asyncio.sleep(self.RATE_LIMIT)
-            await del_sent.unpin()
+            for channel in ctx.guild.text_channels:
+                await deleting.edit(content=f'\N{WASTEBASKET} **Deleting** {user.mention}**\'s messages from** {channel.mention}')
 
-            await ctx.send('\N{WASTEBASKET} `{}` **of** <@{}>**\'s messages left in** {}****'.format(len(history) - c, user, ctx.guild.name))
+                deleted = await channel.purge(check=lambda m: m.author.id == user.id, before=pruning, limit=None)
+                c += len(deleted)
 
-        except exc.CheckFail:
+            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}')