diff --git a/.gitignore b/.gitignore index a68779da8b3eb7877b954989d98fb168649f0815..6041bfc1eb7aab1ee2992dfe534be344978498e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,7 @@ # Caches __pycache__/* */__pycache__ -Plex-bot/cogs/__pycache__ -Develoment-bot/cogs/__pycache__ - -# Temp files -.invisible # Idea files .idea/* -# Plex bot related files -Plex-bot/config.ini -Plex-bot/*/test.py - -# Develoment bot related -Develoment-bot/config.ini diff --git a/Music-bot/bot.py b/Music-bot/bot.py index f1431d1ff35a1c61c29d3056b3f39b2c11744d07..8ee3768f126e1a29b2f7271502fa6077998abb00 100644 --- a/Music-bot/bot.py +++ b/Music-bot/bot.py @@ -20,7 +20,7 @@ bot = commands.Bot(command_prefix=get_prefix, help_attrs=help_attrs, description @bot.event async def on_ready(): bot.uptime = datetime.datetime.utcnow() - print('Logged in as:\n{0} (ID: {0.id})'.format(bot.user)) + print(f'\n\nLogged in as: {bot.user.name} - {bot.user.id}\nAPI Version: {discord.__version__}\n') @bot.event diff --git a/Music-bot/cogs/error_handler.py b/Music-bot/cogs/error_handler.py index 154e81f9bba24c2f6ae7e80aef3d178e7ad223c8..0b33bc15e17a4b7e6a88aad00fecdbc450632393 100644 --- a/Music-bot/cogs/error_handler.py +++ b/Music-bot/cogs/error_handler.py @@ -8,7 +8,7 @@ class CommandErrorHandler: def __init__(self, bot): self.bot = bot - async def on_command_error(self, error, ctx): + async def on_command_error(self, ctx, error): """The event triggered when an error is raised while invoking a command. ctx : Context error : Exception""" diff --git a/Music-bot/cogs/meta.py b/Music-bot/cogs/meta.py index e6c70495312eb4b3e711180ae1d578d6a736380e..e44c297a07425c5fdc10aaa54976b582c4e71002 100644 --- a/Music-bot/cogs/meta.py +++ b/Music-bot/cogs/meta.py @@ -1,33 +1,9 @@ import discord import datetime from discord.ext import commands -from .utils import checks, formats from collections import Counter -class TimeParser: - def __init__(self, argument): - compiled = re.compile(r"(?:(?P<hours>\d+)h)?(?:(?P<minutes>\d+)m)?(?:(?P<seconds>\d+)s)?") - self.original = argument - try: - self.seconds = int(argument) - except ValueError as e: - match = compiled.match(argument) - if match is None or not match.group(0): - raise commands.BadArgument('Failed to parse time.') from e - - self.seconds = 0 - hours = match.group('hours') - if hours is not None: - self.seconds += int(hours) * 3600 - minutes = match.group('minutes') - if minutes is not None: - self.seconds += int(minutes) * 60 - seconds = match.group('seconds') - if seconds is not None: - self.seconds += int(seconds) - - class Meta: """Commands for utilities related to Discord or the Bot itself.""" @@ -52,7 +28,7 @@ class Meta: await self.bot.say('Uptime: **{}**'.format(self.get_bot_uptime())) @commands.command(name='quit', hidden=True) - @checks.is_owner() + @commands.is_owner() async def _quit(self): """Quits the bot.""" await self.bot.logout() diff --git a/Music-bot/cogs/misc.py b/Music-bot/cogs/misc.py index d5173d8b41931d6208a26bd479754a6bf6628c55..0f806191ce76d783a1bd3e2decc1a366c2a1d6ff 100644 --- a/Music-bot/cogs/misc.py +++ b/Music-bot/cogs/misc.py @@ -17,4 +17,4 @@ class Misc: def setup(bot): - bot.add_cog(Misc(bot)) \ No newline at end of file + bot.add_cog(Misc(bot)) diff --git a/Music-bot/cogs/mod.py b/Music-bot/cogs/mod.py deleted file mode 100644 index 3567300ab43f2ae779667d75b8fb8e07cee600e6..0000000000000000000000000000000000000000 --- a/Music-bot/cogs/mod.py +++ /dev/null @@ -1,313 +0,0 @@ -# Borrowed from https://github.com/Rapptz/RoboDanny/tree/async - -from discord.ext import commands -from .utils import config, checks -from collections import Counter -import discord - - -class Mod: - """Moderation related commands.""" - - def __init__(self, bot): - self.bot = bot - self.config = config.Config('mod.json', loop=bot.loop) - - def bot_user(self, message): - return message.server.me if message.channel.is_private else self.bot.user - - @commands.group(pass_context=True, no_pm=True) - @checks.admin_or_permissions(manage_channel=True) - async def ignore(self, ctx): - """Handles the bot's ignore lists. - To use these commands, you must have the Bot Admin role or have - Manage Channel permissions. These commands are not allowed to be used - in a private message context. - Users with Manage Roles or Bot Admin role can still invoke the bot - in ignored channels. - """ - if ctx.invoked_subcommand is None: - await self.bot.say('Invalid subcommand passed: {0.subcommand_passed}'.format(ctx)) - - @ignore.command(name='channel', pass_context=True) - async def channel_cmd(self, ctx, *, channel: discord.Channel = None): - """Ignores a specific channel from being processed. - If no channel is specified, the current channel is ignored. - If a channel is ignored then the bot does not process commands in that - channel until it is unignored. - """ - - if channel is None: - channel = ctx.message.channel - - ignored = self.config.get('ignored', []) - if channel.id in ignored: - await self.bot.say('That channel is already ignored.') - return - - ignored.append(channel.id) - await self.config.put('ignored', ignored) - await self.bot.say('\U0001f44c') - - @ignore.command(name='all', pass_context=True) - @checks.admin_or_permissions(manage_server=True) - async def _all(self, ctx): - """Ignores every channel in the server from being processed. - This works by adding every channel that the server currently has into - the ignore list. If more channels are added then they will have to be - ignored by using the ignore command. - To use this command you must have Manage Server permissions along with - Manage Channel permissions. You could also have the Bot Admin role. - """ - - ignored = self.config.get('ignored', []) - channels = ctx.message.server.channels - ignored.extend(c.id for c in channels if c.type == discord.ChannelType.text) - await self.config.put('ignored', list(set(ignored))) # make unique - await self.bot.say('\U0001f44c') - - @commands.command(pass_context=True, no_pm=True) - @checks.admin_or_permissions(manage_channel=True) - async def unignore(self, ctx, *, channel: discord.Channel = None): - """Unignores a specific channel from being processed. - If no channel is specified, it unignores the current channel. - To use this command you must have the Manage Channel permission or have the - Bot Admin role. - """ - - if channel is None: - channel = ctx.message.channel - - # a set is the proper data type for the ignore list - # however, JSON only supports arrays and objects not sets. - ignored = self.config.get('ignored', []) - try: - ignored.remove(channel.id) - except ValueError: - await self.bot.say('Channel was not ignored in the first place.') - else: - await self.bot.say('\U0001f44c') - - @commands.command(pass_context=True, no_pm=True) - @checks.mod_or_permissions(manage_messages=True) - async def cleanup(self, ctx, search: int = 100): - """Cleans up the bot's messages from the channel. - If a search number is specified, it searches that many messages to delete. - If the bot has Manage Messages permissions, then it will try to delete - messages that look like they invoked the bot as well. - After the cleanup is completed, the bot will send you a message with - which people got their messages deleted and their count. This is useful - to see which users are spammers. - To use this command you must have Manage Messages permission or have the - Bot Mod role. - """ - - spammers = Counter() - channel = ctx.message.channel - prefixes = self.bot.command_prefix - - def is_possible_command_invoke(entry): - valid_call = any(entry.content.startswith(prefix) for prefix in prefixes) - return valid_call and not entry.content[1:2].isspace() - - async for entry in self.bot.logs_from(channel, limit=search): - if entry.author == self.bot.user: - await self.bot.delete_message(entry) - spammers['Bot'] += 1 - if is_possible_command_invoke(entry): - try: - await self.bot.delete_message(entry) - except discord.Forbidden: - continue - else: - spammers[entry.author.name] += 1 - - await self.bot.say('Clean up completed. {} message(s) were deleted.'.format(sum(spammers.values()))) - - spammers = sorted(spammers.items(), key=lambda t: t[1], reverse=True) - stats = '\n'.join(map(lambda t: '- **{0[0]}**: {0[1]}'.format(t), spammers)) - await self.bot.whisper(stats) - - @commands.command(no_pm=True) - @checks.admin_or_permissions(kick_members=True) - async def kick(self, *, member: discord.Member): - """Kicks a member from the server. - In order for this to work, the bot must have Kick Member permissions. - To use this command you must have Kick Members permission or have the - Bot Admin role. - """ - - try: - await self.bot.kick(member) - except discord.Forbidden: - await self.bot.say('The bot does not have permissions to kick members.') - except discord.HTTPException: - await self.bot.say('Kicking failed.') - else: - await self.bot.say('\U0001f44c') - - @commands.command(no_pm=True) - @checks.admin_or_permissions(ban_members=True) - async def ban(self, *, member: discord.Member): - """Bans a member from the server. - In order for this to work, the bot must have Ban Member permissions. - To use this command you must have Ban Members permission or have the - Bot Admin role. - """ - - try: - await self.bot.ban(member) - except discord.Forbidden: - await self.bot.say('The bot does not have permissions to ban members.') - except discord.HTTPException: - await self.bot.say('Banning failed.') - else: - await self.bot.say('\U0001f44c') - - @commands.command(no_pm=True) - @checks.admin_or_permissions(ban_members=True) - async def softban(self, *, member: discord.Member): - """Soft bans a member from the server. - A softban is basically banning the member from the server but - then unbanning the member as well. This allows you to essentially - kick the member while removing their messages. - To use this command you must have Ban Members permissions or have - the Bot Admin role. Note that the bot must have the permission as well. - """ - - try: - await self.bot.ban(member) - await self.bot.unban(member.server, member) - except discord.Forbidden: - await self.bot.say('The bot does not have permissions to ban members.') - except discord.HTTPException: - await self.bot.say('Banning failed.') - else: - await self.bot.say('\U0001f44c') - - @commands.command(no_pm=True) - @checks.admin_or_permissions(manage_server=True) - async def plonk(self, *, member: discord.Member): - """Bans a user from using the bot. - Note that this ban is **global**. So they are banned from - all servers that they access the bot with. So use this with - caution. - There is no way to bypass a plonk regardless of role or permissions. - The only person who cannot be plonked is the bot creator. So this - must be used with caution. - To use this command you must have the Manage Server permission - or have a Bot Admin role. - """ - - plonks = self.config.get('plonks', []) - if member.id in plonks: - await self.bot.say('That user is already bot banned.') - return - - plonks.append(member.id) - await self.config.put('plonks', plonks) - await self.bot.say('{0.name} has been banned from using the bot.'.format(member)) - - @commands.command(no_pm=True) - @checks.admin_or_permissions(manage_server=True) - async def unplonk(self, *, member: discord.Member): - """Unbans a user from using the bot. - To use this command you must have the Manage Server permission - or have a Bot Admin role. - """ - - plonks = self.config.get('plonks', []) - - try: - plonks.remove(member.id) - except ValueError: - pass - else: - await self.config.put('plonks', plonks) - await self.bot.say('{0.name} has been unbanned from using the bot.'.format(member)) - - @commands.command(pass_context=True, no_pm=True) - @checks.admin_or_permissions(manage_roles=True) - async def colour(self, ctx, colour: discord.Colour, *, role: discord.Role): - """Changes the colour of a role. - To use this command you must have the Manage Roles permission or - have the Bot Admin role. The bot must also have Manage Roles permissions. - This command cannot be used in a private message. - """ - try: - await self.bot.edit_role(ctx.message.server, role, colour=colour) - except discord.Forbidden: - await self.bot.say('The bot must have Manage Roles permissions to use this.') - else: - await self.bot.say('\U0001f44c') - - @commands.group(pass_context=True, no_pm=True) - @checks.admin_or_permissions(manage_messages=True) - async def remove(self, ctx): - """Removes messages that meet a criteria. - In order to use this command, you must have Manage Messages permissions - or have the Bot Admin role. Note that the bot needs Manage Messages as - well. These commands cannot be used in a private message. - When the command is done doing its work, you will get a private message - detailing which users got removed and how many messages got removed. - """ - - if ctx.invoked_subcommand is None: - await self.bot.say('Invalid criteria passed "{0.subcommand_passed}"'.format(ctx)) - - async def do_removal(self, channel, limit, predicate): - spammers = Counter() - async for message in self.bot.logs_from(channel, limit=limit): - if predicate(message): - try: - await self.bot.delete_message(message) - except discord.Forbidden: - await self.bot.say('The bot does not have permissions to delete messages.') - return - else: - spammers[message.author.name] += 1 - - await self.bot.say('{} messages(s) were removed.'.format(sum(spammers.values()))) - spammers = sorted(spammers.items(), key=lambda t: t[1], reverse=True) - stats = '\n'.join(map(lambda t: '**{0[0]}**: {0[1]}'.format(t), spammers)) - await self.bot.whisper(stats) - - @remove.command(pass_context=True) - async def embeds(self, ctx, search=100): - """Removes messages that have embeds in them.""" - await self.do_removal(ctx.message.channel, search, lambda e: len(e.embeds)) - - @remove.command(pass_context=True) - async def files(self, ctx, search=100): - """Removes messages that have attachments in them.""" - await self.do_removal(ctx.message.channel, search, lambda e: len(e.attachments)) - - @remove.command(pass_context=True) - async def images(self, ctx, search=100): - """Removes messages that have embeds or attachments.""" - await self.do_removal(ctx.message.channel, search, lambda e: len(e.embeds) or len(e.attachments)) - - @remove.command(name='all', pass_context=True) - async def _remove_all(self, ctx, search=100): - """Removes all messages.""" - await self.do_removal(ctx.message.channel, search, lambda e: True) - - @remove.command(pass_context=True) - async def user(self, ctx, member: discord.Member, search=100): - """Removes all messages by the member.""" - await self.do_removal(ctx.message.channel, search, lambda e: e.author == member) - - @remove.command(pass_context=True) - async def contains(self, ctx, *, substr: str): - """Removes all messages containing a substring. - The substring must be at least 3 characters long. - """ - if len(substr) < 3: - await self.bot.say('The substring length must be at least 3 characters.') - return - - await self.do_removal(ctx.message.channel, 100, lambda e: substr in e.content) - - -def setup(bot): - bot.add_cog(Mod(bot)) diff --git a/Music-bot/cogs/owner.py b/Music-bot/cogs/owner.py new file mode 100644 index 0000000000000000000000000000000000000000..feee084ba93fb6648fd2566cbd2297a476dabf42 --- /dev/null +++ b/Music-bot/cogs/owner.py @@ -0,0 +1,52 @@ +from discord.ext import commands + + +class OwnerCog: + + def __init__(self, bot): + self.bot = bot + + # Hidden means it won't show up on the default help. + @commands.command(name='load', hidden=True) + @commands.is_owner() + async def cog_load(self, ctx, *, cog: str): + """Command which Loads a Module. + Remember to use dot path. e.g: cogs.owner""" + + try: + self.bot.load_extension(cog) + except Exception as e: + await ctx.send(f'**`ERROR:`** {type(e).__name__} - {e}') + else: + await ctx.send('**`SUCCESS`**') + + @commands.command(name='unload', hidden=True) + @commands.is_owner() + async def cog_unload(self, ctx, *, cog: str): + """Command which Unloads a Module. + Remember to use dot path. e.g: cogs.owner""" + + try: + self.bot.unload_extension(cog) + except Exception as e: + await ctx.send(f'**`ERROR:`** {type(e).__name__} - {e}') + else: + await ctx.send('**`SUCCESS`**') + + @commands.command(name='reload', hidden=True) + @commands.is_owner() + async def cog_reload(self, ctx, *, cog: str): + """Command which Reloads a Module. + Remember to use dot path. e.g: cogs.owner""" + + try: + self.bot.unload_extension(cog) + self.bot.load_extension(cog) + except Exception as e: + await ctx.send(f'**`ERROR:`** {type(e).__name__} - {e}') + else: + await ctx.send('**`SUCCESS`**') + + +def setup(bot): + bot.add_cog(OwnerCog(bot)) \ No newline at end of file diff --git a/Music-bot/cogs/utils/checks.py b/Music-bot/cogs/utils/checks.py deleted file mode 100644 index 9be203cd746bbf76bbabc7a2c6c275a2ef3450c4..0000000000000000000000000000000000000000 --- a/Music-bot/cogs/utils/checks.py +++ /dev/null @@ -1,50 +0,0 @@ -# Borrowed from https://github.com/Rapptz/RoboDanny/tree/async - -from discord.ext import commands -import discord.utils - - -def is_owner_check(message): - return message.author.id == '157970669261422592' - - -def is_owner(): - return commands.check(lambda ctx: is_owner_check(ctx.message)) - - -def check_permissions(ctx, perms): - msg = ctx.message - if is_owner_check(msg): - return True - - ch = msg.channel - author = msg.author - resolved = ch.permissions_for(author) - return all(getattr(resolved, name, None) == value for name, value in perms.items()) - - -def role_or_permissions(ctx, check, **perms): - if check_permissions(ctx, perms): - return True - - ch = ctx.message.channel - author = ctx.message.author - if ch.is_private: - return False # can't have roles in PMs - - role = discord.utils.find(check, author.roles) - return role is not None - - -def mod_or_permissions(**perms): - def predicate(ctx): - return role_or_permissions(ctx, lambda r: r.name in ('Bot Mod', 'Bot Admin'), **perms) - - return commands.check(predicate) - - -def admin_or_permissions(**perms): - def predicate(ctx): - return role_or_permissions(ctx, lambda r: r.name == 'Bot Admin', **perms) - - return commands.check(predicate) diff --git a/Music-bot/cogs/utils/formats.py b/Music-bot/cogs/utils/formats.py index 759aa2e03ae7f4b1ecd474f31b052c1d19ecc5af..8089930cceaf29108e7111e8526418185793ac4c 100644 --- a/Music-bot/cogs/utils/formats.py +++ b/Music-bot/cogs/utils/formats.py @@ -8,20 +8,4 @@ async def entry_to_code(bot, entries): for name, entry in entries: output.append(fmt.format(name, entry, width=width)) output.append('```') - await bot.say('\n'.join(output)) - - -async def too_many_matches(bot, msg, matches, entry): - check = lambda m: m.content.isdigit() - await bot.say('There are too many matches... Which one did you mean?') - await bot.say('\n'.join(map(entry, enumerate(matches, 1)))) - - # only give them 3 tries. - for i in range(3): - message = await bot.wait_for_message(author=msg.author, channel=msg.channel, check=check) - index = int(message.content) - try: - return matches[index - 1] - except: - await bot.say('Please give me a valid number. {} tries remaining...'.format(2 - i)) - raise ValueError('Too many tries. Goodbye.') \ No newline at end of file + await bot.say('\n'.join(output)) \ No newline at end of file