diff --git a/core/bot.py b/core/bot.py index 17efd4136..35e34c3ab 100644 --- a/core/bot.py +++ b/core/bot.py @@ -1,6 +1,6 @@ from discord.ext import commands from collections import Counter -from core.utils.helpers import JsonGuildDB +from core.settings import CoreDB from enum import Enum import os @@ -8,9 +8,9 @@ import os class Red(commands.Bot): def __init__(self, cli_flags, **kwargs): self._shutdown_mode = ExitCodes.CRITICAL - self.db = JsonGuildDB("core/data/settings.json", - autosave=True, - create_dirs=True) + self.db = CoreDB("core/data/settings.json", + autosave=True, + create_dirs=True) def prefix_manager(bot, message): global_prefix = self.db.get_global("prefix", []) diff --git a/core/checks.py b/core/checks.py index ac612f464..1852eaf49 100644 --- a/core/checks.py +++ b/core/checks.py @@ -4,4 +4,68 @@ from discord.ext import commands def is_owner(**kwargs): async def check(ctx): return await ctx.bot.is_owner(ctx.author, **kwargs) - return commands.check(check) \ No newline at end of file + return commands.check(check) + + +async def check_permissions(ctx, perms): + if await ctx.bot.is_owner(ctx.author): + return True + elif not perms: + return False + resolved = ctx.channel.permissions_for(ctx.author) + + return all(getattr(resolved, name, None) == value for name, value in perms.items()) + + +def mod_or_permissions(**perms): + async def predicate(ctx): + has_perms_or_is_owner = await check_permissions(ctx, perms) + if ctx.guild is None: + return has_perms_or_is_owner + author = ctx.author + mod_role = ctx.bot.db.get_mod_role(ctx.guild) + admin_role = ctx.bot.db.get_admin_role(ctx.guild) + is_staff = mod_role in author.roles or admin_role in author.roles + is_guild_owner = author == ctx.guild.owner + + return is_staff or has_perms_or_is_owner or is_guild_owner + + return commands.check(predicate) + + +def admin_or_permissions(**perms): + async def predicate(ctx): + has_perms_or_is_owner = await check_permissions(ctx, perms) + if ctx.guild is None: + return has_perms_or_is_owner + author = ctx.author + is_guild_owner = author == ctx.guild.owner + admin_role = ctx.bot.db.get_admin_role(ctx.guild) + + return admin_role in author.roles or has_perms_or_is_owner or is_guild_owner + + return commands.check(predicate) + + +def guildowner_or_permissions(**perms): + async def predicate(ctx): + has_perms_or_is_owner = await check_permissions(ctx, perms) + if ctx.guild is None: + return has_perms_or_is_owner + is_guild_owner = ctx.author == ctx.guild.owner + + return is_guild_owner or has_perms_or_is_owner + + return commands.check(predicate) + + +def guildowner(): + return guildowner_or_permissions() + + +def admin(): + return admin_or_permissions() + + +def mod(): + return mod_or_permissions() \ No newline at end of file diff --git a/core/settings.py b/core/settings.py index 08fcbe179..02ea56d3c 100644 --- a/core/settings.py +++ b/core/settings.py @@ -1,17 +1,88 @@ +from core.utils.helpers import JsonGuildDB +import discord import argparse -# Do we even need a Settings class this time? To be decided - -class Settings: - def __init__(self): - args = {} - self.coowners = [] +class CoreDB(JsonGuildDB): + """ + The central DB used by Red to store a variety + of settings, both global and guild specific + """ def can_login(self): """Used on start to determine if Red is setup enough to login""" raise NotImplementedError + def get_admin_role(self, guild): + """Returns the guild's admin role + + Returns None if not set or if the role + couldn't be retrieved""" + _id = self.get_all(guild, {}).get("admin_role", None) + return discord.utils.get(guild.roles, id=_id) + + def get_mod_role(self, guild): + """Returns the guild's mod role + + Returns None if not set or if the role + couldn't be retrieved""" + _id = self.get_all(guild, {}).get("mod_role", None) + return discord.utils.get(guild.roles, id=_id) + + async def set_admin_role(self, role): + """Sets the admin role for the guild""" + if not isinstance(role, discord.Role): + raise TypeError("A valid Discord role must be passed.") + await self.set(role.guild, "admin_role", role.id) + + async def set_mod_role(self, role): + """Sets the mod role for the guild""" + if not isinstance(role, discord.Role): + raise TypeError("A valid Discord role must be passed.") + await self.set(role.guild, "mod_role", role.id) + + def get_global_whitelist(self): + """Returns the global whitelist""" + return self.get_global("whitelist", []) + + def get_global_blacklist(self): + """Returns the global whitelist""" + return self.get_global("blacklist", []) + + async def set_global_whitelist(self, whitelist): + """Sets the global whitelist""" + if not isinstance(list, whitelist): + raise TypeError("A list of IDs must be passed.") + await self.set_global("whitelist", whitelist) + + async def set_global_blacklist(self, blacklist): + """Sets the global blacklist""" + if not isinstance(list, blacklist): + raise TypeError("A list of IDs must be passed.") + await self.set_global("blacklist", blacklist) + + def get_guild_whitelist(self, guild): + """Returns the guild's whitelist""" + return self.get(guild, "whitelist", []) + + def get_guild_blacklist(self, guild): + """Returns the guild's blacklist""" + return self.get(guild, "blacklist", []) + + async def set_guild_whitelist(self, guild, whitelist): + """Sets the guild's whitelist""" + if not isinstance(guild, discord.Guild) or not isinstance(whitelist, list): + raise TypeError("A valid Discord guild and a list of IDs " + "must be passed.") + await self.set(guild, "whitelist", whitelist) + + async def set_guild_blacklist(self, guild, blacklist): + """Sets the guild's blacklist""" + if not isinstance(guild, discord.Guild) or not isinstance(blacklist, list): + raise TypeError("A valid Discord guild and a list of IDs " + "must be passed.") + await self.set(guild, "blacklist", blacklist) + def parse_cli_flags(): parser = argparse.ArgumentParser(description="Red - Discord Bot") @@ -54,4 +125,4 @@ def parse_cli_flags(): else: args.prefix = [] - return args \ No newline at end of file + return args diff --git a/core/utils/helpers.py b/core/utils/helpers.py index 472b0d605..9f6187292 100644 --- a/core/utils/helpers.py +++ b/core/utils/helpers.py @@ -152,7 +152,7 @@ class JsonGuildDB(JsonDB): await self.save() return value - def get_all(self, guild, default): + def get_all(self, guild, default=None): """Returns all entries of a guild""" if not isinstance(guild, discord.Guild): raise TypeError('Can only get guild data') @@ -182,7 +182,7 @@ class JsonGuildDB(JsonDB): """Removes a global value""" if GLOBAL_KEY not in self._data: self._data[GLOBAL_KEY] = {} - del self._data[key] + del self._data[GLOBAL_KEY][key] await self.save() async def pop_global(self, key, default=None):