diff --git a/cogs/admin.py b/cogs/admin.py new file mode 100644 index 0000000..1a6f95a --- /dev/null +++ b/cogs/admin.py @@ -0,0 +1,485 @@ +from discord.ext import commands + +from . import utils + +import discord +import re + +class Administration: + + + @commands.command(enabled=False) + @commands.guild_only() + @utils.custom_perms(manage_guild=True) + async def restrict(self, ctx, *options): + """ + This is an intuitive command to restrict something to something + The format is `!restrict what who/where` + + For example, `!restrict command role` will require a user to have `role` + to be able to run `command` + `!restrict command channel` will only allow `command` to be ran in `channel` + """ + pass + + @commands.command(aliases=['nick']) + @commands.guild_only() + @utils.custom_perms(kick_members=True) + async def nickname(self, ctx, *, name=None): + """Used to set the nickname for Bonfire (provide no nickname and it will reset) + + EXAMPLE: !nick Music Bot + RESULT: My nickname is now Music Bot""" + try: + await ctx.message.guild.me.edit(nick=name) + except discord.HTTPException: + await ctx.send("Sorry but I can't change my nickname to {}".format(name)) + else: + await ctx.send("\N{OK HAND SIGN}") + + @commands.command() + @commands.guild_only() + @utils.custom_perms(manage_guild=True) + async def ignore(self, ctx, member_or_channel): + """This command can be used to have Bonfire ignore certain members/channels + + EXAMPLE: !ignore #general + RESULT: Bonfire will ignore commands sent in the general channel""" + key = str(ctx.message.guild.id) + + converter = commands.converter.MemberConverter() + converter.prepare(ctx, member_or_channel) + member = None + channel = None + try: + member = converter.convert() + except commands.converter.BadArgument: + converter = commands.converter.TextChannelConverter() + converter.prepare(ctx, member_or_channel) + try: + channel = converter.convert() + except commands.converter.BadArgument: + await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel)) + return + + settings = await utils.get_content('server_settings', key) + if settings is None: + settings = {} + ignored = settings.get('ignored', {'members': [], 'channels': []}) + if member: + if str(member.id) in ignored['members']: + await ctx.send("I am already ignoring {}!".format(member.display_name)) + return + elif member.guild_permissions >= ctx.message.author.guild_permissions: + await ctx.send("You cannot make me ignore someone at equal or higher rank than you!") + return + else: + ignored['members'].append(str(member.id)) + fmt = "Ignoring {}".format(member.display_name) + elif channel: + if str(channel.id) in ignored['channels']: + await ctx.send("I am already ignoring {}!".format(channel.mention)) + return + else: + ignored['channels'].append(str(channel.id)) + fmt = "Ignoring {}".format(channel.mention) + + update = {'ignored': ignored} + await utils.update_content('server_settings', update, key) + await ctx.send(fmt) + + + @commands.command() + @commands.guild_only() + @utils.custom_perms(manage_guild=True) + async def unignore(self, ctx, member_or_channel): + """This command can be used to have Bonfire stop ignoring certain members/channels + + EXAMPLE: !unignore #general + RESULT: Bonfire will no longer ignore commands sent in the general channel""" + key = str(ctx.message.guild.id) + + converter = commands.converter.MemberConverter() + converter.prepare(ctx, member_or_channel) + member = None + channel = None + try: + member = converter.convert() + except commands.converter.BadArgument: + converter = commands.converter.TextChannelConverter() + converter.prepare(ctx, member_or_channel) + try: + channel = converter.convert() + except commands.converter.BadArgument: + await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel)) + return + + settings = await utils.get_content('server_settings', key) + if settings is None: + settings = {} + ignored = settings.get('ignored', {'members': [], 'channels': []}) + if member: + if str(member.id) not in ignored['members']: + await ctx.send("I'm not even ignoring {}!".format(member.display_name)) + return + + ignored['members'].remove(str(member.id)) + fmt = "I am no longer ignoring {}".format(member.display_name) + elif channel: + if str(channel.id) not in ignored['channels']: + await ctx.send("I'm not even ignoring {}!".format(channel.mention)) + return + + ignored['channels'].remove(str(channel.id)) + fmt = "I am no longer ignoring {}".format(channel.mention) + + update = {'ignored': ignored} + await utils.update_content('server_settings', update, key) + await ctx.send(fmt) + + @commands.command(aliases=['alerts']) + @commands.guild_only() + @utils.custom_perms(kick_members=True) + async def notifications(self, ctx, channel: discord.TextChannel): + """This command is used to set a channel as the server's 'notifications' channel + Any notifications (like someone going live on Twitch, or Picarto) will go to that channel + + EXAMPLE: !alerts #alerts + RESULT: No more alerts spammed in #general!""" + key = str(ctx.message.guild.id) + entry = {'server_id': key, + 'notification_channel': str(channel.id)} + if not await utils.update_content('server_settings', entry, key): + await utils.add_content('server_settings', entry) + await ctx.send("I have just changed this server's 'notifications' channel" + "\nAll notifications will now go to `{}`".format(channel)) + + @commands.command() + @commands.guild_only() + @utils.custom_perms(kick_members=True) + async def usernotify(self, ctx, on_off: str): + """This command can be used to set whether or not you want user notificaitons to show + Provide on, yes, or true to set it on; otherwise it will be turned off + + EXAMPLE: !usernotify on + RESULT: Annying join/leave notifications! Yay!""" + # Join/Leave notifications can be kept separate from normal alerts + # So we base this channel on it's own and not from alerts + # When mod logging becomes available, that will be kept to it's own channel if wanted as well + on_off = True if re.search("(on|yes|true)", on_off.lower()) else False + key = str(ctx.message.guild.id) + entry = {'server_id': key, + 'join_leave': on_off} + if not await utils.update_content('server_settings', entry, key): + await utils.add_content('server_settings', entry) + + fmt = "notify" if on_off else "not notify" + await ctx.send("This server will now {} if someone has joined or left".format(fmt)) + + @commands.group() + async def nsfw(self, ctx): + """Handles adding or removing a channel as a nsfw channel""" + # This command isn't meant to do anything, so just send an error if an invalid subcommand is passed + pass + + @nsfw.command(name="add") + @utils.custom_perms(kick_members=True) + async def nsfw_add(self, ctx): + """Registers this channel as a 'nsfw' channel + + EXAMPLE: !nsfw add + RESULT: ;)""" + + if type(ctx.message.channel) is discord.DMChannel: + key = 'DMs' + else: + key = str(ctx.message.guild.id) + + entry = {'server_id': key, + 'nsfw_channels': [str(ctx.message.channel.id)]} + update = {'nsfw_channels': r.row['nsfw_channels'].append(str(ctx.message.channel.id))} + + server_settings = await utils.get_content('server_settings', key) + if server_settings and 'nsfw_channels' in server_settings.keys(): + await utils.update_content('server_settings', update, key) + elif server_settings: + await utils.update_content('server_settings', entry, key) + else: + await utils.add_content('server_settings', entry) + + await ctx.send("This channel has just been registered as 'nsfw'! Have fun you naughties ;)") + + @nsfw.command(name="remove", aliases=["delete"]) + @utils.custom_perms(kick_members=True) + async def nsfw_remove(self, ctx): + """Removes this channel as a 'nsfw' channel + + EXAMPLE: !nsfw remove + RESULT: ;(""" + + if type(ctx.message.channel) is discord.DMChannel: + key = 'DMs' + else: + key = str(ctx.message.guild.id) + + server_settings = await utils.get_content('server_settings', key) + channel = str(ctx.message.channel.id) + try: + channels = server_settings['nsfw_channels'] + if channel in channels: + channels.remove(channel) + + entry = {'nsfw_channels': channels} + await utils.update_content('server_settings', entry, key) + await ctx.send("This channel has just been unregistered as a nsfw channel") + return + except (TypeError, IndexError): + pass + + await ctx.send("This channel is not registered as a 'nsfw' channel!") + + + @commands.group(invoke_without_command=True) + @commands.guild_only() + @utils.custom_perms(send_messages=True) + async def perms(self, ctx, *, command: str = None): + """This command can be used to print the current allowed permissions on a specific command + This supports groups as well as subcommands; pass no argument to print a list of available permissions + + EXAMPLE: !perms help RESULT: Hopefully a result saying you just need send_messages permissions; otherwise lol + this server's admin doesn't like me """ + if command is None: + await ctx.send( + "Valid permissions are: ```\n{}```".format("\n".join("{}".format(i) for i in valid_perms))) + return + + cmd = self.bot.get_command(command) + + if cmd is None: + await ctx.send("That is not a valid command!") + return + + server_settings = await utils.get_content('server_settings', str(ctx.message.guild.id)) + try: + server_perms = server_settings['permissions'] + except (TypeError, IndexError, KeyError): + server_perms = {} + + perms_value = server_perms.get(cmd.qualified_name) + if perms_value is None: + # If we don't find custom permissions, get the required permission for a command + # based on what we set in utils.custom_perms, if custom_perms isn't found, we'll get an IndexError + try: + custom_perms = [func for func in cmd.checks if "custom_perms" in func.__qualname__][0] + except IndexError: + # Loop through and check if there is a check called is_owner + # If we loop through and don't find one, this means that the only other choice is to be + # Able to manage the server (for the utils on perm commands) + for func in cmd.checks: + if "is_owner" in func.__qualname__: + await ctx.send("You need to own the bot to run this command") + return + await ctx.send( + "You are required to have `manage_guild` permissions to run `{}`".format(cmd.qualified_name)) + return + + # Perms will be an attribute if custom_perms is found no matter what, so no need to check this + perms = "\n".join(attribute for attribute, setting in custom_perms.perms.items() if setting) + await ctx.send( + "You are required to have `{}` permissions to run `{}`".format(perms, cmd.qualified_name)) + else: + # Permissions are saved as bit values, so create an object based on that value + # Then check which permission is true, that is our required permission + # There's no need to check for errors here, as we ensure a permission is valid when adding it + permissions = discord.Permissions(perms_value) + needed_perm = [perm[0] for perm in permissions if perm[1]][0] + await ctx.send("You need to have the permission `{}` " + "to use the command `{}` in this server".format(needed_perm, command)) + + @perms.command(name="add", aliases=["setup,create"]) + @commands.guild_only() + @commands.has_permissions(manage_guild=True) + async def add_perms(self, ctx, *msg: str): + """Sets up custom permissions on the provided command + Format must be 'perms add ' + If you want to open the command to everyone, provide 'none' as the permission + + EXAMPLE: !perms add skip ban_members + RESULT: No more random people voting to skip a song""" + + # Since subcommands exist, base the last word in the list as the permission, and the rest of it as the command + command = " ".join(msg[0:len(msg) - 1]) + if command == "": + await ctx.send("Please provide the permissions you want to setup, the format for this must be in:\n" + "`perms add `") + return + try: + permissions = msg[len(msg) - 1] + except IndexError: + await ctx.send("Please provide the permissions you want to setup, the format for this must be in:\n" + "`perms add `") + return + + cmd = self.bot.get_command(command) + + if cmd is None: + await ctx.send( + "That command does not exist! You can't have custom permissions on a non-existant command....") + return + + # If a user can run a command, they have to have send_messages permissions; so use this as the base + if permissions.lower() == "none": + permissions = "send_messages" + + # Convert the string to an int value of the permissions object, based on the required permission + # If we hit an attribute error, that means the permission given was not correct + perm_obj = discord.Permissions.none() + try: + setattr(perm_obj, permissions, True) + except AttributeError: + await ctx.send("{} does not appear to be a valid permission! Valid permissions are: ```\n{}```" + .format(permissions, "\n".join(valid_perms))) + return + perm_value = perm_obj.value + + # Two cases I use should never have custom permissions setup on them, is_owner for obvious reasons + # The other case is if I'm using the default has_permissions case + # Which means I do not want to check custom permissions at all + # Currently the second case is only on adding and removing permissions, to avoid abuse on these + for check in cmd.checks: + if "is_owner" == check.__name__ or "has_permissions" in str(check): + await ctx.send("This command cannot have custom permissions setup!") + return + + key = str(ctx.message.guild.id) + entry = {'server_id': key, + 'permissions': {cmd.qualified_name: perm_value}} + + if not await utils.update_content('server_settings', entry, key): + await utils.add_content('server_settings', entry) + + await ctx.send("I have just added your custom permissions; " + "you now need to have `{}` permissions to use the command `{}`".format(permissions, command)) + + @perms.command(name="remove", aliases=["delete"]) + @commands.guild_only() + @commands.has_permissions(manage_guild=True) + async def remove_perms(self, ctx, *, command: str): + """Removes the custom permissions setup on the command specified + + EXAMPLE: !perms remove play + RESULT: Freedom!""" + + cmd = self.bot.get_command(command) + + if cmd is None: + await ctx.send( + "That command does not exist! You can't have custom permissions on a non-existant command....") + return + + update = {'permissions': {cmd.qualified_name: None}} + await utils.update_content('server_settings', update, str(ctx.message.guild.id)) + await ctx.send("I have just removed the custom permissions for {}!".format(cmd)) + + @commands.command() + @commands.guild_only() + @utils.custom_perms(manage_guild=True) + async def prefix(self, ctx, *, prefix: str): + """This command can be used to set a custom prefix per server + + EXAMPLE: !prefix new_prefix + RESULT: You probably screwing it up and not realizing you now need to do new_prefixprefix""" + key = str(ctx.message.guild.id) + if len(prefix.strip()) > 20: + await ctx.send("Please keep prefixes under 20 characters") + return + if prefix.lower().strip() == "none": + prefix = None + + entry = {'server_id': key, + 'prefix': prefix} + + if not await utils.update_content('server_settings', entry, key): + await utils.add_content('prefixes', entry) + + if prefix is None: + fmt = "I have just cleared your custom prefix, the default prefix will have to be used now" + else: + fmt = "I have just updated the prefix for this server; you now need to call commands with `{0}`. " \ + "For example, you can call this command again with {0}prefix".format(prefix) + await ctx.send(fmt) + + @commands.group(aliases=['rule'], invoke_without_command=True) + @commands.guild_only() + @utils.custom_perms(send_messages=True) + async def rules(self, ctx, rule: int = None): + """This command can be used to view the current rules on the server + + EXAMPLE: !rules 5 + RESULT: Rule 5 is printed""" + server_settings = await utils.get_content('server_settings', str(ctx.message.guild.id)) + if server_settings is None: + await ctx.send("This server currently has no rules on it! I see you like to live dangerously...") + return + + rules = server_settings.get('rules') + + if not rules or len(rules) == 0: + await ctx.send("This server currently has no rules on it! I see you like to live dangerously...") + return + + if rule is None: + try: + pages = utils.Pages(self.bot, message=ctx.message, entries=rules, per_page=5) + pages.title = "Rules for {}".format(ctx.message.guild.name) + await pages.paginate() + except utils.CannotPaginate as e: + await ctx.send(str(e)) + else: + try: + fmt = rules[rule - 1] + except IndexError: + await ctx.send("That rules does not exist.") + return + await ctx.send("Rule {}: \"{}\"".format(rule, fmt)) + + @rules.command(name='add', aliases=['create']) + @commands.guild_only() + @utils.custom_perms(manage_guild=True) + async def rules_add(self, ctx, *, rule: str): + """Adds a rule to this server's rules + + EXAMPLE: !rules add No fun allowed in this server >:c + RESULT: No more fun...unless they break the rules!""" + key = str(ctx.message.guild.id) + entry = {'server_id': key, + 'rules': [rule]} + update = {'rules': r.row['rules'].append(rule)} + + server_settings = await utils.get_content('server_settings', key) + if server_settings and 'rules' in server_settings.keys(): + await utils.update_content('server_settings', update, key) + elif server_settings: + await utils.update_content('server_settings', entry, key) + else: + await utils.add_content('server_settings', entry) + + await ctx.send("I have just saved your new rule, use the rules command to view this server's current rules") + + @rules.command(name='remove', aliases=['delete']) + @commands.guild_only() + @utils.custom_perms(manage_guild=True) + async def rules_delete(self, ctx, rule: int): + """Removes one of the rules from the list of this server's rules + Provide a number to delete that rule + + EXAMPLE: !rules delete 5 + RESULT: Freedom from opression!""" + update = {'rules': r.row['rules'].delete_at(rule - 1)} + if not await utils.update_content('server_settings', update, str(ctx.message.guild.id)): + await ctx.send("That is not a valid rule number, try running the command again.") + else: + await ctx.send("I have just removed that rule from your list of rules!") + +def setup(bot): + bot.add_cog(Administration(bot)) diff --git a/cogs/images.py b/cogs/images.py new file mode 100644 index 0000000..0bf58ad --- /dev/null +++ b/cogs/images.py @@ -0,0 +1,208 @@ +from discord.ext import commands +import discord +import random +import re +import math +from bs4 import BeautifulSoup as bs + +from . import utils + +class Images: + def __init__(self, bot): + self.bot = bot + + @commands.command(aliases=['rc']) + @utils.custom_perms(send_messages=True) + async def cat(self, ctx): + """Use this to print a random cat image. + + EXAMPLE: !cat + RESULT: A beautiful picture of a cat o3o""" + result = await utils.request('http://random.cat/meow') + if result is None: + await ctx.send("I couldn't connect! Sorry no cats right now ;w;") + return + filename = result.get('file', None) + if filename is None: + await ctx.send("I couldn't connect! Sorry no cats right now ;w;") + return + + image = await utils.download_image(filename) + filename = re.search('.*\/i\/(.*)', filename).group(1) + f = discord.File(image, filename=filename) + await ctx.send(file=f) + + + @commands.command(aliases=['dog', 'rd']) + @utils.custom_perms(send_messages=True) + async def doggo(self, ctx): + """Use this to print a random doggo image. + + EXAMPLE: !doggo + RESULT: A beautiful picture of a dog o3o""" + result = await utils.request('http://random.dog', attr='text') + try: + soup = bs(result, 'html.parser') + filename = soup.img.get('src') + except (TypeError, AttributeError): + await ctx.send("I couldn't connect! Sorry no dogs right now ;w;") + return + + image = await utils.download_image("http://random.dog/{}".format(filename)) + f = discord.File(image, filename=filename) + await ctx.send(file=f) + + @commands.command() + @utils.custom_perms(send_messages=True) + async def snek(self, ctx): + """Use this to print a random snek image. + + EXAMPLE: !snek + RESULT: A beautiful picture of a snek o3o""" + # Find a random image based on how many we currently have + f = random.SystemRandom().choice(glob.glob('images/snek*')) + with open(f, 'rb') as f: + await ctx.send(file=discord.File(f)) + + @commands.command() + @commands.guild_only() + @utils.custom_perms(send_messages=True) + async def avatar(self, ctx, member: discord.Member = None): + """Provides an image for the provided person's avatar (yours if no other member is provided) + + EXAMPLE: !avatar @person + RESULT: A full image of that person's avatar""" + + if member is None: + member = ctx.message.author + + url = member.avatar_url + if '.gif' not in url: + url = member.avatar_url_as(format='png') + filename = 'avatar.png' + else: + filename = 'avatar.gif' + if ctx.message.guild.me.permissions_in(ctx.message.channel).attach_files: + filedata = await utils.download_image(url) + if filedata is None: + await ctx.send(url) + else: + f = discord.File(filedata, filename=filename) + await ctx.send(file=f) + else: + await ctx.send(url) + + @commands.command() + @utils.custom_perms(send_messages=True) + async def derpi(self, ctx, *search: str): + """Provides a random image from the first page of derpibooru.org for the following term + + EXAMPLE: !derpi Rainbow Dash + RESULT: A picture of Rainbow Dash!""" + await ctx.message.channel.trigger_typing() + + if len(search) > 0: + url = 'https://derpibooru.org/search.json' + + # Ensure a filter was not provided, as we either want to use our own, or none (for safe pics) + query = ' '.join(value for value in search if not re.search('&?filter_id=[0-9]+', value)) + params = {'q': query} + + nsfw = await utils.channel_is_nsfw(ctx.message.channel) + # If this is a nsfw channel, we just need to tack on 'explicit' to the terms + # Also use the custom filter that I have setup, that blocks some certain tags + # If the channel is not nsfw, we don't need to do anything, as the default filter blocks explicit + if nsfw: + params['q'] += ", (explicit OR suggestive)" + params['filter_id'] = 95938 + else: + params['q'] += ", safe" + # Lets filter out some of the "crap" that's on derpibooru by requiring an image with a score higher than 15 + params['q'] += ', score.gt:15' + + try: + # Get the response from derpibooru and parse the 'search' result from it + data = await utils.request(url, payload=params) + + if data is None: + await ctx.send("Sorry but I failed to connect to Derpibooru!") + return + results = data['search'] + except KeyError: + await ctx.send("No results with that search term, {0}!".format(ctx.message.author.mention)) + return + + # The first request we've made ensures there are results + # Now we can get the total count from that, and make another request based on the number of pages as well + if len(results) > 0: + # Get the total number of pages + pages = math.ceil(data['total'] / len(results)) + # Set a new paramater to set which page to use, randomly based on the number of pages + params['page'] = random.SystemRandom().randint(1, pages) + data = await utils.request(url, payload=params) + if data is None: + await ctx.send("Sorry but I failed to connect to Derpibooru!") + return + # Now get the results again + results = data['search'] + + # Get the image link from the now random page'd and random result from that page + index = random.SystemRandom().randint(0, len(results) - 1) + #image_link = 'https://derpibooru.org/{}'.format(results[index]['id']) + image_link = 'https:{}'.format(results[index]['image']) + else: + await ctx.send("No results with that search term, {0}!".format(ctx.message.author.mention)) + return + else: + # If no search term was provided, search for a random image + # .url will be the URL we end up at, not the one requested. + # https://derpibooru.org/images/random redirects to a random image, so this is exactly what we want + image_link = await utils.request('https://derpibooru.org/images/random', attr='url') + await ctx.send(image_link) + + @commands.command() + @utils.custom_perms(send_messages=True) + async def e621(self, ctx, *, tags: str): + """Searches for a random image from e621.net + Format for the search terms need to be 'search term 1, search term 2, etc.' + If the channel the command is ran in, is registered as a nsfw channel, this image will be explicit + + EXAMPLE: !e621 dragon + RESULT: A picture of a dragon (hopefully, screw your tagging system e621)""" + await ctx.message.channel.trigger_typing() + + # This changes the formatting for queries, so we don't + # Have to use e621's stupid formatting when using the command + + tags = tags.replace(' ', '_') + tags = tags.replace(',_', ' ') + + url = 'https://e621.net/post/index.json' + params = {'limit': 320, + 'tags': tags} + + nsfw = await utils.channel_is_nsfw(ctx.message.channel) + + # e621 by default does not filter explicit content, so tack on + # safe/explicit based on if this channel is nsfw or not + params['tags'] += " rating:explicit" if nsfw else " rating:safe" + + data = await utils.request(url, payload=params) + + if data is None: + await ctx.send("Sorry, I had trouble connecting at the moment; please try again later") + return + + # Try to find an image from the list. If there were no results, we're going to attempt to find + # A number between (0,-1) and receive an error. + # The response should be in a list format, so we'll end up getting a key error if the response was in json + # i.e. it responded with a 404/504/etc. + try: + rand_image = data[random.SystemRandom().randint(0, len(data) - 1)]['file_url'] + await ctx.send(rand_image) + except (ValueError, KeyError): + await ctx.send("No results with that tag {}".format(ctx.message.author.mention)) + return + +def setup(bot): + bot.add_cog(Stats(bot)) diff --git a/cogs/interaction.py b/cogs/interaction.py index f469247..ba37382 100644 --- a/cogs/interaction.py +++ b/cogs/interaction.py @@ -123,34 +123,6 @@ class Interaction: fmt = random.SystemRandom().choice(hugs) await ctx.send(fmt.format(user.display_name)) - @commands.command() - @commands.guild_only() - @utils.custom_perms(send_messages=True) - async def avatar(self, ctx, member: discord.Member = None): - """Provides an image for the provided person's avatar (yours if no other member is provided) - - EXAMPLE: !avatar @person - RESULT: A full image of that person's avatar""" - - if member is None: - member = ctx.message.author - - url = member.avatar_url - if '.gif' not in url: - url = member.avatar_url_as(format='png') - filename = 'avatar.png' - else: - filename = 'avatar.gif' - if ctx.message.guild.me.permissions_in(ctx.message.channel).attach_files: - filedata = await utils.download_image(url) - if filedata is None: - await ctx.send(url) - else: - f = discord.File(filedata, filename=filename) - await ctx.send(file=f) - else: - await ctx.send(url) - @commands.group(invoke_without_command=True) @commands.guild_only() @commands.cooldown(1, 180, BucketType.user) diff --git a/cogs/links.py b/cogs/links.py index e76a408..755d6ab 100644 --- a/cogs/links.py +++ b/cogs/links.py @@ -174,117 +174,6 @@ class Links: except KeyError: await ctx.send("Sorry but I failed to connect to urban dictionary!") - @commands.command() - @utils.custom_perms(send_messages=True) - async def derpi(self, ctx, *search: str): - """Provides a random image from the first page of derpibooru.org for the following term - - EXAMPLE: !derpi Rainbow Dash - RESULT: A picture of Rainbow Dash!""" - await ctx.message.channel.trigger_typing() - - if len(search) > 0: - url = 'https://derpibooru.org/search.json' - - # Ensure a filter was not provided, as we either want to use our own, or none (for safe pics) - query = ' '.join(value for value in search if not re.search('&?filter_id=[0-9]+', value)) - params = {'q': query} - - nsfw = await utils.channel_is_nsfw(ctx.message.channel) - # If this is a nsfw channel, we just need to tack on 'explicit' to the terms - # Also use the custom filter that I have setup, that blocks some certain tags - # If the channel is not nsfw, we don't need to do anything, as the default filter blocks explicit - if nsfw: - params['q'] += ", (explicit OR suggestive)" - params['filter_id'] = 95938 - else: - params['q'] += ", safe" - # Lets filter out some of the "crap" that's on derpibooru by requiring an image with a score higher than 15 - params['q'] += ', score.gt:15' - - try: - # Get the response from derpibooru and parse the 'search' result from it - data = await utils.request(url, payload=params) - - if data is None: - await ctx.send("Sorry but I failed to connect to Derpibooru!") - return - results = data['search'] - except KeyError: - await ctx.send("No results with that search term, {0}!".format(ctx.message.author.mention)) - return - - # The first request we've made ensures there are results - # Now we can get the total count from that, and make another request based on the number of pages as well - if len(results) > 0: - # Get the total number of pages - pages = math.ceil(data['total'] / len(results)) - # Set a new paramater to set which page to use, randomly based on the number of pages - params['page'] = random.SystemRandom().randint(1, pages) - data = await utils.request(url, payload=params) - if data is None: - await ctx.send("Sorry but I failed to connect to Derpibooru!") - return - # Now get the results again - results = data['search'] - - # Get the image link from the now random page'd and random result from that page - index = random.SystemRandom().randint(0, len(results) - 1) - image_link = 'https://derpibooru.org/{}'.format(results[index]['id']) - else: - await ctx.send("No results with that search term, {0}!".format(ctx.message.author.mention)) - return - else: - # If no search term was provided, search for a random image - # .url will be the URL we end up at, not the one requested. - # https://derpibooru.org/images/random redirects to a random image, so this is exactly what we want - image_link = await utils.request('https://derpibooru.org/images/random', attr='url') - await ctx.send(image_link) - - @commands.command() - @utils.custom_perms(send_messages=True) - async def e621(self, ctx, *, tags: str): - """Searches for a random image from e621.net - Format for the search terms need to be 'search term 1, search term 2, etc.' - If the channel the command is ran in, is registered as a nsfw channel, this image will be explicit - - EXAMPLE: !e621 dragon - RESULT: A picture of a dragon (hopefully, screw your tagging system e621)""" - await ctx.message.channel.trigger_typing() - - # This changes the formatting for queries, so we don't - # Have to use e621's stupid formatting when using the command - - tags = tags.replace(' ', '_') - tags = tags.replace(',_', ' ') - - url = 'https://e621.net/post/index.json' - params = {'limit': 320, - 'tags': tags} - - nsfw = await utils.channel_is_nsfw(ctx.message.channel) - - # e621 by default does not filter explicit content, so tack on - # safe/explicit based on if this channel is nsfw or not - params['tags'] += " rating:explicit" if nsfw else " rating:safe" - - data = await utils.request(url, payload=params) - - if data is None: - await ctx.send("Sorry, I had trouble connecting at the moment; please try again later") - return - - # Try to find an image from the list. If there were no results, we're going to attempt to find - # A number between (0,-1) and receive an error. - # The response should be in a list format, so we'll end up getting a key error if the response was in json - # i.e. it responded with a 404/504/etc. - try: - rand_image = data[random.SystemRandom().randint(0, len(data) - 1)]['file_url'] - await ctx.send(rand_image) - except (ValueError, KeyError): - await ctx.send("No results with that tag {}".format(ctx.message.author.mention)) - return - def setup(bot): bot.add_cog(Links(bot)) diff --git a/cogs/core.py b/cogs/misc.py similarity index 70% rename from cogs/core.py rename to cogs/misc.py index d873968..718bea7 100644 --- a/cogs/core.py +++ b/cogs/misc.py @@ -14,13 +14,12 @@ import datetime import psutil -class Core: +class Miscallaneous: """Core commands, these are the miscallaneous commands that don't fit into other categories'""" def __init__(self, bot): self.bot = bot - # This is a dictionary used to hold information about which page a certain help message is on - self.help_embeds = {} + self.help_embeds = [] self.results_per_page = 10 self.commands = None self.process = psutil.Process() @@ -35,55 +34,54 @@ class Core: EXAMPLE: !help help RESULT: This information""" + groups = {} + entries = [] - cmd = None - page = 1 - - if message is not None: - # If something is provided, it can either be the page number or a command - # Try to convert to an int (the page number), if not, then a command should have been provided - try: - page = int(message) - except: - cmd = self.bot.get_command(message) - - if cmd is None: - entries = [] - for cmd in utils.get_all_commands(self.bot): - if await cmd.can_run(ctx): - entries.append(cmd.qualified_name) - try: - pages = utils.Pages(self.bot, message=ctx.message, entries=entries) - await pages.paginate(start_page=page) - except utils.CannotPaginate as e: - await ctx.send(str(e)) - else: - # Get the description for a command - description = cmd.help - if description is not None: - # Split into examples, results, and the description itself based on the string - example = [x.replace('EXAMPLE: ', '') for x in description.split('\n') if 'EXAMPLE:' in x] - result = [x.replace('RESULT: ', '') for x in description.split('\n') if 'RESULT:' in x] - description = [x for x in description.split('\n') if x and 'EXAMPLE:' not in x and 'RESULT:' not in x] + for cmd in utils.get_all_commands(self.bot): + cog = cmd.cog_name + if cog in groups: + groups[cog].append(cmd) else: - example = None - result = None - # Also get the subcommands for this command, if they exist - subcommands = [x.qualified_name for x in utils.get_all_subcommands(cmd) if x.qualified_name != cmd.qualified_name] + groups[cog] = [cmd] - # The rest is simple, create the embed, set the thumbail to me, add all fields if they exist - embed = discord.Embed(title=cmd.qualified_name) - embed.set_thumbnail(url=self.bot.user.avatar_url) - if description: - embed.add_field(name="Description", value="\n".join(description), inline=False) - if example: - embed.add_field(name="Example", value="\n".join(example), inline=False) - if result: - embed.add_field(name="Result", value="\n".join(result), inline=False) - if subcommands: - embed.add_field(name='Subcommands', value="\n".join(subcommands), inline=False) + for cog, cmds in groups.items(): + entry = {'title': "{} Commands".format(cog), + 'fields': []} - await ctx.send(embed=embed) + for cmd in cmds: + if not cmd.help: + # Assume if there's no description for a command, it's not supposed to be used + # I.e. the !command command. It's just a parent + continue + description = cmd.help.partition('\n')[0] + entry['fields'].append({ + 'name': "**{}**".format(cmd.qualified_name), + 'value': description, + 'inline': False + }) + entries.append(entry) + + try: + pages = utils.DetailedPages(self.bot, message=ctx.message, entries=entries) + pages.embed.set_thumbnail(url=ctx.message.guild.me.avatar_url) + await pages.paginate() + except utils.CannotPaginate as e: + await ctx.send(str(e)) + + + @commands.command() + @utils.custom_perms(send_messages=True) + async def say(self, ctx, *, msg: str): + """Tells the bot to repeat what you say + + EXAMPLE: !say I really like orange juice + RESULT: I really like orange juice""" + fmt = "\u200B{}".format(msg) + await ctx.send(fmt) + try: + await ctx.message.delete() + except: + pass @commands.command() @utils.custom_perms(send_messages=True) @@ -167,7 +165,7 @@ class Core: cal = calendar.TextCalendar().formatmonth(year, month) await ctx.send("```\n{}```".format(cal)) - @commands.command() + @commands.command(aliases=['about']) @utils.custom_perms(send_messages=True) async def info(self, ctx): """This command can be used to print out some of my information""" @@ -253,59 +251,6 @@ class Core: await ctx.send("Use this URL to add me to a server that you'd like!\n{}" .format(discord.utils.oauth_url(app_info.id, perms))) - @commands.command(aliases=['rc']) - @utils.custom_perms(send_messages=True) - async def cat(self, ctx): - """Use this to print a random cat image. - - EXAMPLE: !cat - RESULT: A beautiful picture of a cat o3o""" - result = await utils.request('http://random.cat/meow') - if result is None: - await ctx.send("I couldn't connect! Sorry no cats right now ;w;") - return - filename = result.get('file', None) - if filename is None: - await ctx.send("I couldn't connect! Sorry no cats right now ;w;") - return - - image = await utils.download_image(filename) - filename = re.search('.*\/i\/(.*)', filename).group(1) - f = discord.File(image, filename=filename) - await ctx.send(file=f) - - - @commands.command(aliases=['dog', 'rd']) - @utils.custom_perms(send_messages=True) - async def doggo(self, ctx): - """Use this to print a random doggo image. - - EXAMPLE: !doggo - RESULT: A beautiful picture of a dog o3o""" - result = await utils.request('http://random.dog', attr='text') - try: - soup = bs(result, 'html.parser') - filename = soup.img.get('src') - except (TypeError, AttributeError): - await ctx.send("I couldn't connect! Sorry no dogs right now ;w;") - return - - image = await utils.download_image("http://random.dog/{}".format(filename)) - f = discord.File(image, filename=filename) - await ctx.send(file=f) - - @commands.command() - @utils.custom_perms(send_messages=True) - async def snek(self, ctx): - """Use this to print a random snek image. - - EXAMPLE: !snek - RESULT: A beautiful picture of a snek o3o""" - # Find a random image based on how many we currently have - f = random.SystemRandom().choice(glob.glob('images/snek*')) - with open(f, 'rb') as f: - await ctx.send(file=discord.File(f)) - @commands.command() @utils.custom_perms(send_messages=True) async def joke(self, ctx): @@ -372,4 +317,4 @@ class Core: def setup(bot): - bot.add_cog(Core(bot)) + bot.add_cog(Miscallaneous(bot)) diff --git a/cogs/mod.py b/cogs/mod.py index 482ed69..8dccef6 100644 --- a/cogs/mod.py +++ b/cogs/mod.py @@ -10,26 +10,13 @@ import rethinkdb as r valid_perms = [p for p in dir(discord.Permissions) if isinstance(getattr(discord.Permissions, p), property)] -class Mod: +class Moderation: """Commands that can be used by a or an admin, depending on the command""" def __init__(self, bot): self.bot = bot - @commands.command(aliases=['nick']) - @commands.guild_only() - @utils.custom_perms(kick_members=True) - async def nickname(self, ctx, *, name=None): - """Used to set the nickname for Bonfire (provide no nickname and it will reset) - EXAMPLE: !nick Music Bot - RESULT: My nickname is now Music Bot""" - try: - await ctx.message.guild.me.edit(nick=name) - except discord.HTTPException: - await ctx.send("Sorry but I can't change my nickname to {}".format(name)) - else: - await ctx.send("\N{OK HAND SIGN}") @commands.command() @commands.guild_only() @@ -108,391 +95,6 @@ class Mod: except discord.HTTPException: await ctx.send("Sorry, I failed to ban that user!") - @commands.command() - @commands.guild_only() - @utils.custom_perms(manage_guild=True) - async def ignore(self, ctx, member_or_channel): - """This command can be used to have Bonfire ignore certain members/channels - - EXAMPLE: !ignore #general - RESULT: Bonfire will ignore commands sent in the general channel""" - key = str(ctx.message.guild.id) - - converter = commands.converter.MemberConverter() - converter.prepare(ctx, member_or_channel) - member = None - channel = None - try: - member = converter.convert() - except commands.converter.BadArgument: - converter = commands.converter.TextChannelConverter() - converter.prepare(ctx, member_or_channel) - try: - channel = converter.convert() - except commands.converter.BadArgument: - await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel)) - return - - settings = await utils.get_content('server_settings', key) - if settings is None: - settings = {} - ignored = settings.get('ignored', {'members': [], 'channels': []}) - if member: - if str(member.id) in ignored['members']: - await ctx.send("I am already ignoring {}!".format(member.display_name)) - return - elif member.guild_permissions >= ctx.message.author.guild_permissions: - await ctx.send("You cannot make me ignore someone at equal or higher rank than you!") - return - else: - ignored['members'].append(str(member.id)) - fmt = "Ignoring {}".format(member.display_name) - elif channel: - if str(channel.id) in ignored['channels']: - await ctx.send("I am already ignoring {}!".format(channel.mention)) - return - else: - ignored['channels'].append(str(channel.id)) - fmt = "Ignoring {}".format(channel.mention) - - update = {'ignored': ignored} - await utils.update_content('server_settings', update, key) - await ctx.send(fmt) - - - @commands.command() - @commands.guild_only() - @utils.custom_perms(manage_guild=True) - async def unignore(self, ctx, member_or_channel): - """This command can be used to have Bonfire stop ignoring certain members/channels - - EXAMPLE: !unignore #general - RESULT: Bonfire will no longer ignore commands sent in the general channel""" - key = str(ctx.message.guild.id) - - converter = commands.converter.MemberConverter() - converter.prepare(ctx, member_or_channel) - member = None - channel = None - try: - member = converter.convert() - except commands.converter.BadArgument: - converter = commands.converter.TextChannelConverter() - converter.prepare(ctx, member_or_channel) - try: - channel = converter.convert() - except commands.converter.BadArgument: - await ctx.send("{} does not appear to be a member or channel!".format(member_or_channel)) - return - - settings = await utils.get_content('server_settings', key) - if settings is None: - settings = {} - ignored = settings.get('ignored', {'members': [], 'channels': []}) - if member: - if str(member.id) not in ignored['members']: - await ctx.send("I'm not even ignoring {}!".format(member.display_name)) - return - - ignored['members'].remove(str(member.id)) - fmt = "I am no longer ignoring {}".format(member.display_name) - elif channel: - if str(channel.id) not in ignored['channels']: - await ctx.send("I'm not even ignoring {}!".format(channel.mention)) - return - - ignored['channels'].remove(str(channel.id)) - fmt = "I am no longer ignoring {}".format(channel.mention) - - update = {'ignored': ignored} - await utils.update_content('server_settings', update, key) - await ctx.send(fmt) - - @commands.command(aliases=['alerts']) - @commands.guild_only() - @utils.custom_perms(kick_members=True) - async def notifications(self, ctx, channel: discord.TextChannel): - """This command is used to set a channel as the server's 'notifications' channel - Any notifications (like someone going live on Twitch, or Picarto) will go to that channel - - EXAMPLE: !alerts #alerts - RESULT: No more alerts spammed in #general!""" - key = str(ctx.message.guild.id) - entry = {'server_id': key, - 'notification_channel': str(channel.id)} - if not await utils.update_content('server_settings', entry, key): - await utils.add_content('server_settings', entry) - await ctx.send("I have just changed this server's 'notifications' channel" - "\nAll notifications will now go to `{}`".format(channel)) - - @commands.command() - @commands.guild_only() - @utils.custom_perms(kick_members=True) - async def usernotify(self, ctx, on_off: str): - """This command can be used to set whether or not you want user notificaitons to show - Provide on, yes, or true to set it on; otherwise it will be turned off - - EXAMPLE: !usernotify on - RESULT: Annying join/leave notifications! Yay!""" - # Join/Leave notifications can be kept separate from normal alerts - # So we base this channel on it's own and not from alerts - # When mod logging becomes available, that will be kept to it's own channel if wanted as well - on_off = True if re.search("(on|yes|true)", on_off.lower()) else False - key = str(ctx.message.guild.id) - entry = {'server_id': key, - 'join_leave': on_off} - if not await utils.update_content('server_settings', entry, key): - await utils.add_content('server_settings', entry) - - fmt = "notify" if on_off else "not notify" - await ctx.send("This server will now {} if someone has joined or left".format(fmt)) - - @commands.group() - async def nsfw(self, ctx): - """Handles adding or removing a channel as a nsfw channel""" - # This command isn't meant to do anything, so just send an error if an invalid subcommand is passed - pass - - @nsfw.command(name="add") - @utils.custom_perms(kick_members=True) - async def nsfw_add(self, ctx): - """Registers this channel as a 'nsfw' channel - - EXAMPLE: !nsfw add - RESULT: ;)""" - - if type(ctx.message.channel) is discord.DMChannel: - key = 'DMs' - else: - key = str(ctx.message.guild.id) - - entry = {'server_id': key, - 'nsfw_channels': [str(ctx.message.channel.id)]} - update = {'nsfw_channels': r.row['nsfw_channels'].append(str(ctx.message.channel.id))} - - server_settings = await utils.get_content('server_settings', key) - if server_settings and 'nsfw_channels' in server_settings.keys(): - await utils.update_content('server_settings', update, key) - elif server_settings: - await utils.update_content('server_settings', entry, key) - else: - await utils.add_content('server_settings', entry) - - await ctx.send("This channel has just been registered as 'nsfw'! Have fun you naughties ;)") - - @nsfw.command(name="remove", aliases=["delete"]) - @utils.custom_perms(kick_members=True) - async def nsfw_remove(self, ctx): - """Removes this channel as a 'nsfw' channel - - EXAMPLE: !nsfw remove - RESULT: ;(""" - - if type(ctx.message.channel) is discord.DMChannel: - key = 'DMs' - else: - key = str(ctx.message.guild.id) - - server_settings = await utils.get_content('server_settings', key) - channel = str(ctx.message.channel.id) - try: - channels = server_settings['nsfw_channels'] - if channel in channels: - channels.remove(channel) - - entry = {'nsfw_channels': channels} - await utils.update_content('server_settings', entry, key) - await ctx.send("This channel has just been unregistered as a nsfw channel") - return - except (TypeError, IndexError): - pass - - await ctx.send("This channel is not registered as a 'nsfw' channel!") - - @commands.command() - @utils.custom_perms(kick_members=True) - async def say(self, ctx, *, msg: str): - """Tells the bot to repeat what you say - - EXAMPLE: !say I really like orange juice - RESULT: I really like orange juice""" - fmt = "\u200B{}".format(msg) - await ctx.send(fmt) - try: - await ctx.message.delete() - except: - pass - - @commands.group(invoke_without_command=True) - @commands.guild_only() - @utils.custom_perms(send_messages=True) - async def perms(self, ctx, *, command: str = None): - """This command can be used to print the current allowed permissions on a specific command - This supports groups as well as subcommands; pass no argument to print a list of available permissions - - EXAMPLE: !perms help RESULT: Hopefully a result saying you just need send_messages permissions; otherwise lol - this server's admin doesn't like me """ - if command is None: - await ctx.send( - "Valid permissions are: ```\n{}```".format("\n".join("{}".format(i) for i in valid_perms))) - return - - cmd = self.bot.get_command(command) - - if cmd is None: - await ctx.send("That is not a valid command!") - return - - server_settings = await utils.get_content('server_settings', str(ctx.message.guild.id)) - try: - server_perms = server_settings['permissions'] - except (TypeError, IndexError, KeyError): - server_perms = {} - - perms_value = server_perms.get(cmd.qualified_name) - if perms_value is None: - # If we don't find custom permissions, get the required permission for a command - # based on what we set in utils.custom_perms, if custom_perms isn't found, we'll get an IndexError - try: - custom_perms = [func for func in cmd.checks if "custom_perms" in func.__qualname__][0] - except IndexError: - # Loop through and check if there is a check called is_owner - # If we loop through and don't find one, this means that the only other choice is to be - # Able to manage the server (for the utils on perm commands) - for func in cmd.checks: - if "is_owner" in func.__qualname__: - await ctx.send("You need to own the bot to run this command") - return - await ctx.send( - "You are required to have `manage_guild` permissions to run `{}`".format(cmd.qualified_name)) - return - - # Perms will be an attribute if custom_perms is found no matter what, so no need to check this - perms = "\n".join(attribute for attribute, setting in custom_perms.perms.items() if setting) - await ctx.send( - "You are required to have `{}` permissions to run `{}`".format(perms, cmd.qualified_name)) - else: - # Permissions are saved as bit values, so create an object based on that value - # Then check which permission is true, that is our required permission - # There's no need to check for errors here, as we ensure a permission is valid when adding it - permissions = discord.Permissions(perms_value) - needed_perm = [perm[0] for perm in permissions if perm[1]][0] - await ctx.send("You need to have the permission `{}` " - "to use the command `{}` in this server".format(needed_perm, command)) - - @perms.command(name="add", aliases=["setup,create"]) - @commands.guild_only() - @commands.has_permissions(manage_guild=True) - async def add_perms(self, ctx, *msg: str): - """Sets up custom permissions on the provided command - Format must be 'perms add ' - If you want to open the command to everyone, provide 'none' as the permission - - EXAMPLE: !perms add skip ban_members - RESULT: No more random people voting to skip a song""" - - # Since subcommands exist, base the last word in the list as the permission, and the rest of it as the command - command = " ".join(msg[0:len(msg) - 1]) - if command == "": - await ctx.send("Please provide the permissions you want to setup, the format for this must be in:\n" - "`perms add `") - return - try: - permissions = msg[len(msg) - 1] - except IndexError: - await ctx.send("Please provide the permissions you want to setup, the format for this must be in:\n" - "`perms add `") - return - - cmd = self.bot.get_command(command) - - if cmd is None: - await ctx.send( - "That command does not exist! You can't have custom permissions on a non-existant command....") - return - - # If a user can run a command, they have to have send_messages permissions; so use this as the base - if permissions.lower() == "none": - permissions = "send_messages" - - # Convert the string to an int value of the permissions object, based on the required permission - # If we hit an attribute error, that means the permission given was not correct - perm_obj = discord.Permissions.none() - try: - setattr(perm_obj, permissions, True) - except AttributeError: - await ctx.send("{} does not appear to be a valid permission! Valid permissions are: ```\n{}```" - .format(permissions, "\n".join(valid_perms))) - return - perm_value = perm_obj.value - - # Two cases I use should never have custom permissions setup on them, is_owner for obvious reasons - # The other case is if I'm using the default has_permissions case - # Which means I do not want to check custom permissions at all - # Currently the second case is only on adding and removing permissions, to avoid abuse on these - for check in cmd.checks: - if "is_owner" == check.__name__ or "has_permissions" in str(check): - await ctx.send("This command cannot have custom permissions setup!") - return - - key = str(ctx.message.guild.id) - entry = {'server_id': key, - 'permissions': {cmd.qualified_name: perm_value}} - - if not await utils.update_content('server_settings', entry, key): - await utils.add_content('server_settings', entry) - - await ctx.send("I have just added your custom permissions; " - "you now need to have `{}` permissions to use the command `{}`".format(permissions, command)) - - @perms.command(name="remove", aliases=["delete"]) - @commands.guild_only() - @commands.has_permissions(manage_guild=True) - async def remove_perms(self, ctx, *, command: str): - """Removes the custom permissions setup on the command specified - - EXAMPLE: !perms remove play - RESULT: Freedom!""" - - cmd = self.bot.get_command(command) - - if cmd is None: - await ctx.send( - "That command does not exist! You can't have custom permissions on a non-existant command....") - return - - update = {'permissions': {cmd.qualified_name: None}} - await utils.update_content('server_settings', update, str(ctx.message.guild.id)) - await ctx.send("I have just removed the custom permissions for {}!".format(cmd)) - - @commands.command() - @commands.guild_only() - @utils.custom_perms(manage_guild=True) - async def prefix(self, ctx, *, prefix: str): - """This command can be used to set a custom prefix per server - - EXAMPLE: !prefix new_prefix - RESULT: You probably screwing it up and not realizing you now need to do new_prefixprefix""" - key = str(ctx.message.guild.id) - if len(prefix.strip()) > 20: - await ctx.send("Please keep prefixes under 20 characters") - return - if prefix.lower().strip() == "none": - prefix = None - - entry = {'server_id': key, - 'prefix': prefix} - - if not await utils.update_content('server_settings', entry, key): - await utils.add_content('prefixes', entry) - - if prefix is None: - fmt = "I have just cleared your custom prefix, the default prefix will have to be used now" - else: - fmt = "I have just updated the prefix for this server; you now need to call commands with `{0}`. " \ - "For example, you can call this command again with {0}prefix".format(prefix) - await ctx.send(fmt) - @commands.command() @commands.guild_only() @utils.custom_perms(manage_messages=True) @@ -576,78 +178,6 @@ class Mod: except: pass - @commands.group(aliases=['rule'], invoke_without_command=True) - @commands.guild_only() - @utils.custom_perms(send_messages=True) - async def rules(self, ctx, rule: int = None): - """This command can be used to view the current rules on the server - - EXAMPLE: !rules 5 - RESULT: Rule 5 is printed""" - server_settings = await utils.get_content('server_settings', str(ctx.message.guild.id)) - if server_settings is None: - await ctx.send("This server currently has no rules on it! I see you like to live dangerously...") - return - - rules = server_settings.get('rules') - - if not rules or len(rules) == 0: - await ctx.send("This server currently has no rules on it! I see you like to live dangerously...") - return - - if rule is None: - try: - pages = utils.Pages(self.bot, message=ctx.message, entries=rules, per_page=5) - pages.title = "Rules for {}".format(ctx.message.guild.name) - await pages.paginate() - except utils.CannotPaginate as e: - await ctx.send(str(e)) - else: - try: - fmt = rules[rule - 1] - except IndexError: - await ctx.send("That rules does not exist.") - return - await ctx.send("Rule {}: \"{}\"".format(rule, fmt)) - - @rules.command(name='add', aliases=['create']) - @commands.guild_only() - @utils.custom_perms(manage_guild=True) - async def rules_add(self, ctx, *, rule: str): - """Adds a rule to this server's rules - - EXAMPLE: !rules add No fun allowed in this server >:c - RESULT: No more fun...unless they break the rules!""" - key = str(ctx.message.guild.id) - entry = {'server_id': key, - 'rules': [rule]} - update = {'rules': r.row['rules'].append(rule)} - - server_settings = await utils.get_content('server_settings', key) - if server_settings and 'rules' in server_settings.keys(): - await utils.update_content('server_settings', update, key) - elif server_settings: - await utils.update_content('server_settings', entry, key) - else: - await utils.add_content('server_settings', entry) - - await ctx.send("I have just saved your new rule, use the rules command to view this server's current rules") - - @rules.command(name='remove', aliases=['delete']) - @commands.guild_only() - @utils.custom_perms(manage_guild=True) - async def rules_delete(self, ctx, rule: int): - """Removes one of the rules from the list of this server's rules - Provide a number to delete that rule - - EXAMPLE: !rules delete 5 - RESULT: Freedom from opression!""" - update = {'rules': r.row['rules'].delete_at(rule - 1)} - if not await utils.update_content('server_settings', update, str(ctx.message.guild.id)): - await ctx.send("That is not a valid rule number, try running the command again.") - else: - await ctx.send("I have just removed that rule from your list of rules!") - def setup(bot): - bot.add_cog(Mod(bot)) + bot.add_cog(Moderation(bot)) diff --git a/cogs/music.py b/cogs/music.py index 6183be4..59fa577 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -303,6 +303,7 @@ class Music: state = self.voice_states.get(ctx.message.guild.id) if state and state.voice and state.voice.channel: await state.voice.move_to(channel) + await ctx.send("Joined {} and ready to play".format(channel.name)) return True else: self.voice_states[ctx.message.guild.id] = VoiceState(ctx.message.guild, self.bot) diff --git a/cogs/roles.py b/cogs/roles.py index c3e3dad..691be9a 100644 --- a/cogs/roles.py +++ b/cogs/roles.py @@ -303,5 +303,107 @@ class Roles: await ctx.send("I have just added the role {} to: ```\n{}```".format(name, fmt)) + @commands.group(invoke_without_command=True, aliases=['assign']) + @commands.guild_only() + @utils.custom_perms(manage_roles=True) + async def assigns(self, ctx, *role: discord.Role): + """Adds the provided role(s) to the list of available self-assignable roles + + EXAMPLE: !assigns Member NSFW + RESULT: Allows users to self-assign the roles Member, and NSFW""" + roles = [str(r.id) for r in role] + key = str(ctx.message.guild.id) + server_settings = await utils.get_content('server_settings', key) + + if server_settings is None: + entry = {'server_id': key, 'self_assignable_roles': roles} + await utils.add_content('server_settings', entry) + else: + self_assignable_roles = server_settings.get('self_assignable_roles', []) + self_assignable_roles.extend(roles) + self_assignable_roles = list(set(self_assignable_roles)) + update = {'self_assignable_roles': self_assignable_roles} + await utils.update_content('server_settings', update, key) + + if len(roles) == 1: + fmt = "Successfully added {} as a self-assignable role".format(role[0].name) + else: + fmt = "Succesfully added the following roles as self-assignable:\n{}".format( + "\n".join(["**{}**".format(r.name) for r in role]) + ) + await ctx.send(fmt) + + @assigns.command(name='remove') + @commands.guild_only() + @utils.custom_perms(manage_roles=True) + async def _remove_assigns(self, ctx, *role: discord.Role): + """Removes the provided role(s) from the list of available self-assignable roles + + EXAMPLE: !assigns remove Member NSFW + RESULT: Removes the ability for users to self-assign the roles Member, and NSFW""" + roles = [str(r.id) for r in role] + key = str(ctx.message.guild.id) + server_settings = await utils.get_content('server_settings', key) + + if server_settings is None: + await ctx.send("There are no self-assignable roles on this server") + return + self_assignable_roles = server_settings.get('self_assignable_roles', []) + if len(self_assignable_roles) == 0: + await ctx.send("There are no self-assignable roles on this server") + return + + fmt = "" + for r in role: + rid = str(r.id) + try: + self_assignable_roles.remove(rid) + except ValueError: + fmt += "\n{} is not a self-assignable role".format(r.name) + else: + fmt += "\n{} is no longer a self-assignable role".format(r.name) + + update = {'self_assignable_roles': self_assignable_roles} + await utils.update_content('server_settings', update, key) + await ctx.send(fmt) + + @assigns.command(name='me') + @commands.guild_only() + @commands.check(utils.is_owner) + async def _me_assigns(self, ctx, *role: discord.Role): + """Assigns the provided role(s) to you, if they can be assigned + + EXAMPLE: !assign me Member + RESULT: You now have the Member role""" + if not ctx.message.guild.me.guild_permissions.manage_roles: + await ctx.send("I need to have manage roles permissions to assign roles") + return + + author = ctx.message.author + key = str(ctx.message.guild.id) + server_settings = await utils.get_content('server_settings', key) + + if server_settings is None: + await ctx.send("There are no self-assignable roles on this server") + return + self_assignable_roles = server_settings.get('self_assignable_roles', []) + if len(self_assignable_roles) == 0: + await ctx.send("There are no self-assignable roles on this server") + return + + fmt = "" + roles = [r for r in role if str(r.id) in self_assignable_roles] + fmt += "\n".join(["Successfully added {}".format(r.name) + if str(r.id) in self_assignable_roles else + "{} is not available to be self-assigned".format(r.name) + for r in role]) + + try: + await author.add_roles(*roles) + await ctx.send(fmt) + except discord.HTTPException: + await ctx.send("I cannot assign roles to you {}".format(author.mention)) + + def setup(bot): bot.add_cog(Roles(bot)) diff --git a/cogs/stats.py b/cogs/stats.py index dfe6187..aab8d69 100644 --- a/cogs/stats.py +++ b/cogs/stats.py @@ -245,7 +245,7 @@ class Stats: @commands.command() @commands.guild_only() @utils.custom_perms(send_messages=True) - async def stats(self, ctx, member: discord.Member = None): + async def battlestats(self, ctx, member: discord.Member = None): """Prints the battling stats for you, or the user provided EXAMPLE: !stats @OtherPerson