From 7c192b366858bb43f1e199efe28863b0ed13be27 Mon Sep 17 00:00:00 2001 From: Twentysix Date: Wed, 15 Mar 2017 22:54:27 +0100 Subject: [PATCH] [Mod] Added optional role hierarchy check Toggleable with [p]modset hierarchy This enables a role hierarchy check before all moderation actions. If the mod doesn't have a role superior to the user's top role the action will be denied. Server owner and bot owner are exempt from the check. --- cogs/mod.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/cogs/mod.py b/cogs/mod.py index 076a99cd6..57e6f8d0c 100644 --- a/cogs/mod.py +++ b/cogs/mod.py @@ -31,9 +31,10 @@ ACTIONS_CASES = { } default_settings = { - "ban_mention_spam" : False, - "delete_repeats" : False, - "mod-log" : None + "ban_mention_spam" : False, + "delete_repeats" : False, + "mod-log" : None, + "respect_hierarchy" : False } @@ -111,14 +112,18 @@ class Mod: await send_cmd_help(ctx) roles = settings.get_server(server).copy() _settings = {**self.settings[server.id], **roles} + if "respect_hierarchy" not in _settings: + _settings["respect_hierarchy"] = default_settings["respect_hierarchy"] if "delete_delay" not in _settings: - _settings["delete_delay"] = -1 + _settings["delete_delay"] = "Disabled" + msg = ("Admin role: {ADMIN_ROLE}\n" "Mod role: {MOD_ROLE}\n" "Mod-log: {mod-log}\n" "Delete repeats: {delete_repeats}\n" "Ban mention spam: {ban_mention_spam}\n" "Delete delay: {delete_delay}\n" + "Respects hierarchy: {respect_hierarchy}" "".format(**_settings)) await self.bot.say(box(msg)) @@ -234,7 +239,7 @@ class Mod: @modset.command(pass_context=True, no_pm=True, name='cases') async def set_cases(self, ctx, action: str = None, enabled: bool = None): """Enables or disables case creation for each type of mod action - + Enabled can be 'on' or 'off'""" server = ctx.message.server @@ -278,6 +283,23 @@ class Mod: ) await self.bot.say(msg) + @modset.command(pass_context=True, no_pm=True) + @checks.serverowner_or_permissions() + async def hierarchy(self, ctx): + """Toggles role hierarchy check for mods / admins""" + server = ctx.message.server + toggled = self.settings[server.id].get("respect_hierarchy", + default_settings["respect_hierarchy"]) + if not toggled: + self.settings[server.id]["respect_hierarchy"] = True + await self.bot.say("Role hierarchy will be checked when " + "moderation commands are issued.") + else: + self.settings[server.id]["respect_hierarchy"] = False + await self.bot.say("Role hierarchy will be ignored when " + "moderation commands are issued.") + dataIO.save_json("data/mod/settings.json", self.settings) + @commands.command(no_pm=True, pass_context=True) @checks.admin_or_permissions(kick_members=True) async def kick(self, ctx, user: discord.Member, *, reason: str = None): @@ -289,6 +311,11 @@ class Mod: await self.bot.say("I cannot let you do that. Self-harm is " "bad \N{PENSIVE FACE}") return + elif not self.is_allowed_by_hierarchy(server, author, user): + await self.bot.say("I cannot let you do that. You are " + "not higher than the user in the role " + "hierarchy.") + return try: await self.bot.kick(user) @@ -319,6 +346,11 @@ class Mod: await self.bot.say("I cannot let you do that. Self-harm is " "bad \N{PENSIVE FACE}") return + elif not self.is_allowed_by_hierarchy(server, author, user): + await self.bot.say("I cannot let you do that. You are " + "not higher than the user in the role " + "hierarchy.") + return if days: if days.isdigit(): @@ -365,6 +397,11 @@ class Mod: await self.bot.say("I cannot let you do that. Self-harm is " "bad \N{PENSIVE FACE}") return + elif not self.is_allowed_by_hierarchy(server, author, user): + await self.bot.say("I cannot let you do that. You are " + "not higher than the user in the role " + "hierarchy.") + return try: invite = await self.bot.create_invite(server, max_age=3600*24) @@ -433,10 +470,17 @@ class Mod: channel = ctx.message.channel server = ctx.message.server overwrites = channel.overwrites_for(user) + if overwrites.send_messages is False: await self.bot.say("That user can't send messages in this " "channel.") return + elif not self.is_allowed_by_hierarchy(server, author, user): + await self.bot.say("I cannot let you do that. You are " + "not higher than the user in the role " + "hierarchy.") + return + self._perms_cache[user.id][channel.id] = overwrites.send_messages overwrites.send_messages = False try: @@ -461,6 +505,13 @@ class Mod: """Mutes user in the server""" author = ctx.message.author server = ctx.message.server + + if not self.is_allowed_by_hierarchy(server, author, user): + await self.bot.say("I cannot let you do that. You are " + "not higher than the user in the role " + "hierarchy.") + return + register = {} for channel in server.channels: if channel.type != discord.ChannelType.text: @@ -506,11 +557,20 @@ class Mod: async def channel_unmute(self, ctx, user : discord.Member): """Unmutes user in the current channel""" channel = ctx.message.channel + author = ctx.message.author + server = ctx.message.server overwrites = channel.overwrites_for(user) + if overwrites.send_messages: await self.bot.say("That user doesn't seem to be muted " "in this channel.") return + elif not self.is_allowed_by_hierarchy(server, author, user): + await self.bot.say("I cannot let you do that. You are " + "not higher than the user in the role " + "hierarchy.") + return + if user.id in self._perms_cache: old_value = self._perms_cache[user.id].get(channel.id) else: @@ -542,11 +602,19 @@ class Mod: async def server_unmute(self, ctx, user : discord.Member): """Unmutes user in the server""" server = ctx.message.server + author = ctx.message.author + if user.id not in self._perms_cache: await self.bot.say("That user doesn't seem to have been muted with {0}mute commands. " "Unmute them in the channels you want with `{0}unmute `" "".format(ctx.prefix)) return + elif not self.is_allowed_by_hierarchy(server, author, user): + await self.bot.say("I cannot let you do that. You are " + "not higher than the user in the role " + "hierarchy.") + return + for channel in server.channels: if channel.type != discord.ChannelType.text: continue @@ -1306,6 +1374,16 @@ class Mod: else: return False + def is_allowed_by_hierarchy(self, server, mod, user): + toggled = self.settings[server.id].get("respect_hierarchy", + default_settings["respect_hierarchy"]) + is_special = mod == server.owner or mod.id == self.bot.settings.owner + + if not toggled: + return True + else: + return mod.top_role.position > user.top_role.position or is_special + async def new_case(self, server, *, action, mod=None, user, reason=None, until=None, channel=None): action_type = action.lower() + "_cases" if not self.settings[server.id].get(action_type, default_settings[action_type]):