[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.
This commit is contained in:
Twentysix
2017-03-15 22:54:27 +01:00
parent 3d9a157516
commit 7c192b3668

View File

@@ -33,7 +33,8 @@ ACTIONS_CASES = {
default_settings = { default_settings = {
"ban_mention_spam" : False, "ban_mention_spam" : False,
"delete_repeats" : False, "delete_repeats" : False,
"mod-log" : None "mod-log" : None,
"respect_hierarchy" : False
} }
@@ -111,14 +112,18 @@ class Mod:
await send_cmd_help(ctx) await send_cmd_help(ctx)
roles = settings.get_server(server).copy() roles = settings.get_server(server).copy()
_settings = {**self.settings[server.id], **roles} _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: if "delete_delay" not in _settings:
_settings["delete_delay"] = -1 _settings["delete_delay"] = "Disabled"
msg = ("Admin role: {ADMIN_ROLE}\n" msg = ("Admin role: {ADMIN_ROLE}\n"
"Mod role: {MOD_ROLE}\n" "Mod role: {MOD_ROLE}\n"
"Mod-log: {mod-log}\n" "Mod-log: {mod-log}\n"
"Delete repeats: {delete_repeats}\n" "Delete repeats: {delete_repeats}\n"
"Ban mention spam: {ban_mention_spam}\n" "Ban mention spam: {ban_mention_spam}\n"
"Delete delay: {delete_delay}\n" "Delete delay: {delete_delay}\n"
"Respects hierarchy: {respect_hierarchy}"
"".format(**_settings)) "".format(**_settings))
await self.bot.say(box(msg)) await self.bot.say(box(msg))
@@ -278,6 +283,23 @@ class Mod:
) )
await self.bot.say(msg) 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) @commands.command(no_pm=True, pass_context=True)
@checks.admin_or_permissions(kick_members=True) @checks.admin_or_permissions(kick_members=True)
async def kick(self, ctx, user: discord.Member, *, reason: str = None): 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 " await self.bot.say("I cannot let you do that. Self-harm is "
"bad \N{PENSIVE FACE}") "bad \N{PENSIVE FACE}")
return 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: try:
await self.bot.kick(user) await self.bot.kick(user)
@@ -319,6 +346,11 @@ class Mod:
await self.bot.say("I cannot let you do that. Self-harm is " await self.bot.say("I cannot let you do that. Self-harm is "
"bad \N{PENSIVE FACE}") "bad \N{PENSIVE FACE}")
return 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:
if days.isdigit(): if days.isdigit():
@@ -365,6 +397,11 @@ class Mod:
await self.bot.say("I cannot let you do that. Self-harm is " await self.bot.say("I cannot let you do that. Self-harm is "
"bad \N{PENSIVE FACE}") "bad \N{PENSIVE FACE}")
return 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: try:
invite = await self.bot.create_invite(server, max_age=3600*24) invite = await self.bot.create_invite(server, max_age=3600*24)
@@ -433,10 +470,17 @@ class Mod:
channel = ctx.message.channel channel = ctx.message.channel
server = ctx.message.server server = ctx.message.server
overwrites = channel.overwrites_for(user) overwrites = channel.overwrites_for(user)
if overwrites.send_messages is False: if overwrites.send_messages is False:
await self.bot.say("That user can't send messages in this " await self.bot.say("That user can't send messages in this "
"channel.") "channel.")
return 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 self._perms_cache[user.id][channel.id] = overwrites.send_messages
overwrites.send_messages = False overwrites.send_messages = False
try: try:
@@ -461,6 +505,13 @@ class Mod:
"""Mutes user in the server""" """Mutes user in the server"""
author = ctx.message.author author = ctx.message.author
server = ctx.message.server 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 = {} register = {}
for channel in server.channels: for channel in server.channels:
if channel.type != discord.ChannelType.text: if channel.type != discord.ChannelType.text:
@@ -506,11 +557,20 @@ class Mod:
async def channel_unmute(self, ctx, user : discord.Member): async def channel_unmute(self, ctx, user : discord.Member):
"""Unmutes user in the current channel""" """Unmutes user in the current channel"""
channel = ctx.message.channel channel = ctx.message.channel
author = ctx.message.author
server = ctx.message.server
overwrites = channel.overwrites_for(user) overwrites = channel.overwrites_for(user)
if overwrites.send_messages: if overwrites.send_messages:
await self.bot.say("That user doesn't seem to be muted " await self.bot.say("That user doesn't seem to be muted "
"in this channel.") "in this channel.")
return 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: if user.id in self._perms_cache:
old_value = self._perms_cache[user.id].get(channel.id) old_value = self._perms_cache[user.id].get(channel.id)
else: else:
@@ -542,11 +602,19 @@ class Mod:
async def server_unmute(self, ctx, user : discord.Member): async def server_unmute(self, ctx, user : discord.Member):
"""Unmutes user in the server""" """Unmutes user in the server"""
server = ctx.message.server server = ctx.message.server
author = ctx.message.author
if user.id not in self._perms_cache: 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. " 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 <user>`" "Unmute them in the channels you want with `{0}unmute <user>`"
"".format(ctx.prefix)) "".format(ctx.prefix))
return 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: for channel in server.channels:
if channel.type != discord.ChannelType.text: if channel.type != discord.ChannelType.text:
continue continue
@@ -1306,6 +1374,16 @@ class Mod:
else: else:
return False 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): async def new_case(self, server, *, action, mod=None, user, reason=None, until=None, channel=None):
action_type = action.lower() + "_cases" action_type = action.lower() + "_cases"
if not self.settings[server.id].get(action_type, default_settings[action_type]): if not self.settings[server.id].get(action_type, default_settings[action_type]):