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__/ 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: diff --git a/src/cogs/management.py b/src/cogs/management.py index 3bd4130..7d6b2f2 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 @@ -28,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 @@ -37,81 +38,102 @@ class Administration: async def _prune_user(self, ctx): pass - @_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 + @_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 - channels = ctx.guild.text_channels - if reference is not None: - for channel in channels: - try: - ref = await channel.get_message(reference) + if not channels: + channels = [ctx.channel] - 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 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) - 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 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 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}') diff --git a/src/cogs/owner.py b/src/cogs/owner.py index fd0b560..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): @@ -192,7 +203,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() diff --git a/src/run.py b/src/run.py index 2f10f4f..1d72368 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 @@ -143,8 +143,14 @@ 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=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) diff --git a/src/utils/utils.py b/src/utils/utils.py index 5d3e33d..a2d8f92 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): @@ -94,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: