diff --git a/changelog.d/3472.enhance.rst b/changelog.d/3472.enhance.rst new file mode 100644 index 00000000000..43dc1fe4d92 --- /dev/null +++ b/changelog.d/3472.enhance.rst @@ -0,0 +1,4 @@ +Add caching for ignored channels/guilds. +Add caching for white/blacklist. +Add consume-rest for white/blacklist commands. +Allow ignoring channel categories. \ No newline at end of file diff --git a/changelog.d/mod/3472.enhance.rst b/changelog.d/mod/3472.enhance.rst new file mode 100644 index 00000000000..87dbf995453 --- /dev/null +++ b/changelog.d/mod/3472.enhance.rst @@ -0,0 +1 @@ +Move ignore commands and checks into core. \ No newline at end of file diff --git a/redbot/cogs/mod/mod.py b/redbot/cogs/mod/mod.py index 9eaa372c579..03454de39c1 100644 --- a/redbot/cogs/mod/mod.py +++ b/redbot/cogs/mod/mod.py @@ -17,7 +17,7 @@ _ = T_ = Translator("Mod", __file__) -__version__ = "1.0.0" +__version__ = "1.1.0" class CompositeMetaClass(type(commands.Cog), type(ABC)): @@ -85,35 +85,36 @@ def cog_unload(self): async def _maybe_update_config(self): """Maybe update `delete_delay` value set by Config prior to Mod 1.0.0.""" - if await self.settings.version(): - return - guild_dict = await self.settings.all_guilds() - for guild_id, info in guild_dict.items(): - delete_repeats = info.get("delete_repeats", False) - if delete_repeats: - val = 3 - else: - val = -1 - await self.settings.guild(discord.Object(id=guild_id)).delete_repeats.set(val) - await self.settings.version.set(__version__) - - # TODO: Move this to core. - # This would be in .movetocore , but the double-under name here makes that more trouble - async def bot_check(self, ctx): - """Global check to see if a channel or server is ignored. - - Any users who have permission to use the `ignore` or `unignore` commands - surpass the check. - """ - perms = ctx.channel.permissions_for(ctx.author) - surpass_ignore = ( - isinstance(ctx.channel, discord.abc.PrivateChannel) - or perms.manage_guild - or await ctx.bot.is_owner(ctx.author) - or await ctx.bot.is_admin(ctx.author) - ) - if surpass_ignore: - return True - guild_ignored = await self.settings.guild(ctx.guild).ignored() - chann_ignored = await self.settings.channel(ctx.channel).ignored() - return not (guild_ignored or chann_ignored and not perms.manage_channels) + if not await self.settings.version(): + guild_dict = await self.settings.all_guilds() + for guild_id, info in guild_dict.items(): + delete_repeats = info.get("delete_repeats", False) + if delete_repeats: + val = 3 + else: + val = -1 + await self.settings.guild(discord.Object(id=guild_id)).delete_repeats.set(val) + await self.settings.version.set("1.0.0") # set version of last update + if await self.settings.version() < "1.1.0": + prefixes = await self.bot.get_valid_prefixes() + msg = _( + "Ignored guilds and channels have been moved. " + "Please use `{prefix}moveignoredchannels` if " + "you were previously using these functions." + ).format(prefix=prefixes[0]) + await self.bot.send_to_owners(msg) + await self.settings.version.set(__version__) + + @commands.command() + @commands.is_owner() + async def moveignoredchannels(self, ctx: commands.Context) -> None: + """Move ignored channels and servers to core""" + all_guilds = await self.settings.all_guilds() + all_channels = await self.settings.all_channels() + for guild_id, settings in all_guilds.items(): + await self.bot._config.guild_from_id(guild_id).ignored.set(settings["ignored"]) + await self.settings.guild_from_id(guild_id).ignored.clear() + for channel_id, settings in all_channels.items(): + await self.bot._config.channel_from_id(channel_id).ignored.set(settings["ignored"]) + await self.settings.channel_fro_id(channel_id).clear() + await ctx.send(_("Ignored channels and guilds restored.")) diff --git a/redbot/cogs/mod/movetocore.py b/redbot/cogs/mod/movetocore.py index e358cc74274..7834bb7eebf 100644 --- a/redbot/cogs/mod/movetocore.py +++ b/redbot/cogs/mod/movetocore.py @@ -47,84 +47,3 @@ async def _delete_helper(m): await asyncio.sleep(delay) await _delete_helper(message) - - # When the below are moved to core, the global check in .modcore needs to be moved as well. - @commands.group() - @commands.guild_only() - @checks.admin_or_permissions(manage_channels=True) - async def ignore(self, ctx: commands.Context): - """Add servers or channels to the ignore list.""" - if ctx.invoked_subcommand is None: - await ctx.send(await self.count_ignored()) - - @ignore.command(name="channel") - async def ignore_channel(self, ctx: commands.Context, channel: discord.TextChannel = None): - """Ignore commands in the channel. - - Defaults to the current channel. - """ - if not channel: - channel = ctx.channel - if not await self.settings.channel(channel).ignored(): - await self.settings.channel(channel).ignored.set(True) - await ctx.send(_("Channel added to ignore list.")) - else: - await ctx.send(_("Channel already in ignore list.")) - - @ignore.command(name="server", aliases=["guild"]) - @checks.admin_or_permissions(manage_guild=True) - async def ignore_guild(self, ctx: commands.Context): - """Ignore commands in this server.""" - guild = ctx.guild - if not await self.settings.guild(guild).ignored(): - await self.settings.guild(guild).ignored.set(True) - await ctx.send(_("This server has been added to the ignore list.")) - else: - await ctx.send(_("This server is already being ignored.")) - - @commands.group() - @commands.guild_only() - @checks.admin_or_permissions(manage_channels=True) - async def unignore(self, ctx: commands.Context): - """Remove servers or channels from the ignore list.""" - if ctx.invoked_subcommand is None: - await ctx.send(await self.count_ignored()) - - @unignore.command(name="channel") - async def unignore_channel(self, ctx: commands.Context, channel: discord.TextChannel = None): - """Remove a channel from ignore the list. - - Defaults to the current channel. - """ - if not channel: - channel = ctx.channel - - if await self.settings.channel(channel).ignored(): - await self.settings.channel(channel).ignored.set(False) - await ctx.send(_("Channel removed from ignore list.")) - else: - await ctx.send(_("That channel is not in the ignore list.")) - - @unignore.command(name="server", aliases=["guild"]) - @checks.admin_or_permissions(manage_guild=True) - async def unignore_guild(self, ctx: commands.Context): - """Remove this server from the ignore list.""" - guild = ctx.message.guild - if await self.settings.guild(guild).ignored(): - await self.settings.guild(guild).ignored.set(False) - await ctx.send(_("This server has been removed from the ignore list.")) - else: - await ctx.send(_("This server is not in the ignore list.")) - - async def count_ignored(self): - ch_count = 0 - svr_count = 0 - for guild in self.bot.guilds: - if not await self.settings.guild(guild).ignored(): - for channel in guild.text_channels: - if await self.settings.channel(channel).ignored(): - ch_count += 1 - else: - svr_count += 1 - msg = _("Currently ignoring:\n{} channels\n{} guilds\n").format(ch_count, svr_count) - return box(msg) diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 601820670e6..e1f6125db55 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -38,7 +38,7 @@ from .events import init_events from .global_checks import init_global_checks -from .settings_caches import PrefixManager +from .settings_caches import PrefixManager, IgnoreManager, WhitelistBlacklistManager from .rpc import RPCMixin from .utils import common_filters @@ -118,13 +118,14 @@ def __init__(self, *args, cli_flags=None, bot_dir: Path = Path.cwd(), **kwargs): admin_role=[], mod_role=[], embeds=None, + ignored=False, use_bot_color=False, fuzzy=False, disabled_commands=[], autoimmune_ids=[], ) - self._config.register_channel(embeds=None) + self._config.register_channel(embeds=None, ignored=False) self._config.register_user(embeds=None) self._config.init_custom(CUSTOM_GROUPS, 2) @@ -133,6 +134,8 @@ def __init__(self, *args, cli_flags=None, bot_dir: Path = Path.cwd(), **kwargs): self._config.init_custom(SHARED_API_TOKENS, 2) self._config.register_custom(SHARED_API_TOKENS) self._prefix_cache = PrefixManager(self._config, cli_flags) + self._ignored_cache = IgnoreManager(self._config) + self._whiteblacklist_cache = WhitelistBlacklistManager(self._config) async def prefix_manager(bot, message) -> List[str]: prefixes = await self._prefix_cache.get_prefixes(message.guild) @@ -350,13 +353,13 @@ async def allowed_by_whitelist_blacklist( if await self.is_owner(who): return True - global_whitelist = await self._config.whitelist() + global_whitelist = await self._whiteblacklist_cache.get_whitelist() if global_whitelist: if who.id not in global_whitelist: return False else: # blacklist is only used when whitelist doesn't exist. - global_blacklist = await self._config.blacklist() + global_blacklist = await self._whiteblacklist_cache.get_blacklist() if who.id in global_blacklist: return False @@ -375,17 +378,44 @@ async def allowed_by_whitelist_blacklist( # there is a silent failure potential, and role blacklist/whitelists will break. ids = {i for i in (who.id, *(getattr(who, "_roles", []))) if i != guild.id} - guild_whitelist = await self._config.guild(guild).whitelist() + guild_whitelist = await self._whiteblacklist_cache.get_whitelist(guild) if guild_whitelist: if ids.isdisjoint(guild_whitelist): return False else: - guild_blacklist = await self._config.guild(guild).blacklist() + guild_blacklist = await self._whiteblacklist_cache.get_blacklist(guild) if not ids.isdisjoint(guild_blacklist): return False return True + async def ignored_channel_or_guild(self, ctx: commands.Context) -> bool: + """ + This checks if the bot is meant to be ignoring commands in a channel or guild, + as considered by Red's whitelist and blacklist. + + Parameters + ---------- + ctx : Context of where the command is being run. + + Returns + ------- + bool + `True` if commands are allowed in the channel, `False` otherwise + """ + perms = ctx.channel.permissions_for(ctx.author) + surpass_ignore = ( + isinstance(ctx.channel, discord.abc.PrivateChannel) + or perms.manage_guild + or await ctx.bot.is_owner(ctx.author) + or await ctx.bot.is_admin(ctx.author) + ) + if surpass_ignore: + return True + guild_ignored = await self._ignored_cache.get_ignored_guild(ctx.guild) + chann_ignored = await self._ignored_cache.get_ignored_channel(ctx.channel) + return not (guild_ignored or chann_ignored and not perms.manage_channels) + async def get_valid_prefixes(self, guild: Optional[discord.Guild] = None) -> List[str]: """ This gets the valid prefixes for a guild. diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index dd9b8f93992..8a037535c9c 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -1702,16 +1702,14 @@ async def whitelist(self, ctx: commands.Context): pass @whitelist.command(name="add") - async def whitelist_add(self, ctx, *, user: Union[discord.Member, int]): + async def whitelist_add(self, ctx: commands.Context, *users: List[Union[discord.Member, int]]): """ Adds a user to the whitelist. """ - uid = getattr(user, "id", user) - async with ctx.bot._config.whitelist() as curr_list: - if uid not in curr_list: - curr_list.append(uid) + uids = [getattr(user, "id", user) for user in users] + await self.bot._whiteblacklist_cache.add_to_whitelist(None, uids) - await ctx.send(_("User added to whitelist.")) + await ctx.send(_("Users added to whitelist.")) @whitelist.command(name="list") async def whitelist_list(self, ctx: commands.Context): @@ -1732,28 +1730,23 @@ async def whitelist_list(self, ctx: commands.Context): await ctx.send(box(page)) @whitelist.command(name="remove") - async def whitelist_remove(self, ctx: commands.Context, *, user: Union[discord.Member, int]): + async def whitelist_remove( + self, ctx: commands.Context, *users: List[Union[discord.Member, int]] + ): """ Removes user from whitelist. """ - removed = False - uid = getattr(user, "id", user) - async with ctx.bot._config.whitelist() as curr_list: - if uid in curr_list: - removed = True - curr_list.remove(uid) + uids = [getattr(user, "id", user) for user in users] + await self.bot._whiteblacklist_cache.remove_from_whitelist(None, uids) - if removed: - await ctx.send(_("User has been removed from whitelist.")) - else: - await ctx.send(_("User was not in the whitelist.")) + await ctx.send(_("Users have been removed from whitelist.")) @whitelist.command(name="clear") async def whitelist_clear(self, ctx: commands.Context): """ Clears the whitelist. """ - await ctx.bot._config.whitelist.set([]) + await self.bot._whiteblacklist_cache.clear_whitelist() await ctx.send(_("Whitelist has been cleared.")) @commands.group() @@ -1765,18 +1758,21 @@ async def blacklist(self, ctx: commands.Context): pass @blacklist.command(name="add") - async def blacklist_add(self, ctx: commands.Context, *, user: Union[discord.Member, int]): + async def blacklist_add(self, ctx: commands.Context, *users: List[Union[discord.Member, int]]): """ Adds a user to the blacklist. """ - if await ctx.bot.is_owner(user): - await ctx.send(_("You cannot blacklist an owner!")) - return + for user in users: + if isinstance(user, int): + user_obj = discord.Object(id=user) + else: + user_obj = user + if await ctx.bot.is_owner(user_obj): + await ctx.send(_("You cannot blacklist an owner!")) + return - uid = getattr(user, "id", user) - async with ctx.bot._config.blacklist() as curr_list: - if uid not in curr_list: - curr_list.append(uid) + uids = [getattr(user, "id", user) for user in users] + await self.bot._whiteblacklist_cache.add_to_blacklist(None, uids) await ctx.send(_("User added to blacklist.")) @@ -1785,7 +1781,7 @@ async def blacklist_list(self, ctx: commands.Context): """ Lists blacklisted users. """ - curr_list = await ctx.bot._config.blacklist() + curr_list = await self.bot._whiteblacklist_cache.get_blacklist(None) if not curr_list: await ctx.send("Blacklist is empty.") @@ -1799,29 +1795,24 @@ async def blacklist_list(self, ctx: commands.Context): await ctx.send(box(page)) @blacklist.command(name="remove") - async def blacklist_remove(self, ctx: commands.Context, *, user: Union[discord.Member, int]): + async def blacklist_remove( + self, ctx: commands.Context, *users: List[Union[discord.Member, int]] + ): """ Removes user from blacklist. """ - removed = False - uid = getattr(user, "id", user) - async with ctx.bot._config.blacklist() as curr_list: - if uid in curr_list: - removed = True - curr_list.remove(uid) + uids = [getattr(user, "id", user) for user in users] + await self.bot._whiteblacklist_cache.remove_from_blacklist(None, uids) - if removed: - await ctx.send(_("User has been removed from blacklist.")) - else: - await ctx.send(_("User was not in the blacklist.")) + await ctx.send(_("Users have been removed from blacklist.")) @blacklist.command(name="clear") async def blacklist_clear(self, ctx: commands.Context): """ Clears the blacklist. """ - await ctx.bot._config.blacklist.set([]) + await self.bot._whiteblacklist_cache.clear_blacklist() await ctx.send(_("Blacklist has been cleared.")) @commands.group() @@ -1835,31 +1826,25 @@ async def localwhitelist(self, ctx: commands.Context): @localwhitelist.command(name="add") async def localwhitelist_add( - self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role, int] + self, + ctx: commands.Context, + *users_or_roles: List[Union[discord.Member, discord.Role, int]], ): """ Adds a user or role to the whitelist. """ - user = isinstance(user_or_role, discord.Member) - if isinstance(user_or_role, int): - user_or_role = discord.Object(id=user_or_role) - user = True + names = [getattr(users_or_roles, "name", users_or_roles) for u_or_r in users_or_roles] + uids = [getattr(users_or_roles, "id", users_or_roles) for u_or_r in users_or_roles] + await self.bot._whiteblacklist_cache.add_to_whitelist(ctx.guild, uids) - async with ctx.bot._config.guild(ctx.guild).whitelist() as curr_list: - if user_or_role.id not in curr_list: - curr_list.append(user_or_role.id) - - if user: - await ctx.send(_("User added to whitelist.")) - else: - await ctx.send(_("Role added to whitelist.")) + await ctx.send(_("{names} added to whitelist.").format(names=humanize_list(names))) @localwhitelist.command(name="list") async def localwhitelist_list(self, ctx: commands.Context): """ Lists whitelisted users and roles. """ - curr_list = await ctx.bot._config.guild(ctx.guild).whitelist() + curr_list = await self.bot._whiteblacklist_cache.get_whitelist(ctx.guild) if not curr_list: await ctx.send("Local whitelist is empty.") @@ -1874,40 +1859,28 @@ async def localwhitelist_list(self, ctx: commands.Context): @localwhitelist.command(name="remove") async def localwhitelist_remove( - self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role, int] + self, + ctx: commands.Context, + *users_or_roles: List[Union[discord.Member, discord.Role, int]], ): """ Removes user or role from whitelist. """ - user = isinstance(user_or_role, discord.Member) - if isinstance(user_or_role, int): - user_or_role = discord.Object(id=user_or_role) - user = True + names = [getattr(users_or_roles, "name", users_or_roles) for u_or_r in users_or_roles] + uids = [getattr(users_or_roles, "id", users_or_roles) for u_or_r in users_or_roles] + await self.bot._whiteblacklist_cache.remove_from_whitelist(ctx.guild, uids) - removed = False - async with ctx.bot._config.guild(ctx.guild).whitelist() as curr_list: - if user_or_role.id in curr_list: - removed = True - curr_list.remove(user_or_role.id) - - if removed: - if user: - await ctx.send(_("User has been removed from whitelist.")) - else: - await ctx.send(_("Role has been removed from whitelist.")) - else: - if user: - await ctx.send(_("User was not in the whitelist.")) - else: - await ctx.send(_("Role was not in the whitelist.")) + await ctx.send( + _("{names} removed from the local whitelist.").format(names=humanize_list(names)) + ) @localwhitelist.command(name="clear") async def localwhitelist_clear(self, ctx: commands.Context): """ Clears the whitelist. """ - await ctx.bot._config.guild(ctx.guild).whitelist.set([]) - await ctx.send(_("Whitelist has been cleared.")) + await self.bot._whiteblacklist_cache.clear_whitelist(ctx.guild) + await ctx.send(_("Local whitelist has been cleared.")) @commands.group() @commands.guild_only() @@ -1920,42 +1893,38 @@ async def localblacklist(self, ctx: commands.Context): @localblacklist.command(name="add") async def localblacklist_add( - self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role, int] + self, + ctx: commands.Context, + *users_or_roles: List[Union[discord.Member, discord.Role, int]], ): """ Adds a user or role to the blacklist. """ - user = isinstance(user_or_role, discord.Member) - if isinstance(user_or_role, int): - user_or_role = discord.Object(id=user_or_role) - user = True - - if user: - if user_or_role.id == ctx.author.id: + for user_or_role in users_or_roles: + uid = discord.Object(id=getattr(user_or_role, "id", user_or_role)) + if uid.id == ctx.author.id: await ctx.send(_("You cannot blacklist yourself!")) return - if user_or_role.id == ctx.guild.owner_id and not await ctx.bot.is_owner(ctx.author): + if uid.id == ctx.guild.owner_id and not await ctx.bot.is_owner(ctx.author): await ctx.send(_("You cannot blacklist the guild owner!")) return - if await ctx.bot.is_owner(user_or_role): + if await ctx.bot.is_owner(uid): await ctx.send(_("You cannot blacklist a bot owner!")) return + names = [getattr(users_or_roles, "name", users_or_roles) for u_or_r in users_or_roles] + uids = [getattr(users_or_roles, "id", users_or_roles) for u_or_r in users_or_roles] + await self.bot._whiteblacklist_cache.add_to_blacklist(ctx.guild, uids) - async with ctx.bot._config.guild(ctx.guild).blacklist() as curr_list: - if user_or_role.id not in curr_list: - curr_list.append(user_or_role.id) - - if user: - await ctx.send(_("User added to blacklist.")) - else: - await ctx.send(_("Role added to blacklist.")) + await ctx.send( + _("{names} added to the local blacklist.").format(names=humanize_list(names)) + ) @localblacklist.command(name="list") async def localblacklist_list(self, ctx: commands.Context): """ Lists blacklisted users and roles. """ - curr_list = await ctx.bot._config.guild(ctx.guild).blacklist() + curr_list = await self.bot._whiteblacklist_cache.get_blacklist(ctx.guild) if not curr_list: await ctx.send("Local blacklist is empty.") @@ -1970,32 +1939,18 @@ async def localblacklist_list(self, ctx: commands.Context): @localblacklist.command(name="remove") async def localblacklist_remove( - self, ctx: commands.Context, *, user_or_role: Union[discord.Member, discord.Role, int] + self, ctx: commands.Context, *users_or_roles: Union[discord.Member, discord.Role, int] ): """ Removes user or role from blacklist. """ - removed = False - user = isinstance(user_or_role, discord.Member) - if isinstance(user_or_role, int): - user_or_role = discord.Object(id=user_or_role) - user = True + names = [getattr(users_or_roles, "name", users_or_roles) for u_or_r in users_or_roles] + uids = [getattr(users_or_roles, "id", users_or_roles) for u_or_r in users_or_roles] + await self.bot._whiteblacklist_cache.remove_from_whitelist(ctx.guild, uids) - async with ctx.bot._config.guild(ctx.guild).blacklist() as curr_list: - if user_or_role.id in curr_list: - removed = True - curr_list.remove(user_or_role.id) - - if removed: - if user: - await ctx.send(_("User has been removed from blacklist.")) - else: - await ctx.send(_("Role has been removed from blacklist.")) - else: - if user: - await ctx.send(_("User was not in the blacklist.")) - else: - await ctx.send(_("Role was not in the blacklist.")) + await ctx.send( + _("{names} removed from the local blacklist.").format(names=humanize_list(names)) + ) @localblacklist.command(name="clear") async def localblacklist_clear(self, ctx: commands.Context): @@ -2003,7 +1958,7 @@ async def localblacklist_clear(self, ctx: commands.Context): Clears the blacklist. """ await ctx.bot._config.guild(ctx.guild).blacklist.set([]) - await ctx.send(_("Blacklist has been cleared.")) + await ctx.send(_("Local blacklist has been cleared.")) @checks.guildowner_or_permissions(administrator=True) @commands.group(name="command") @@ -2409,6 +2364,107 @@ async def rpc_reload(self, request): await self.rpc_unload(request) await self.rpc_load(request) + @commands.group() + @commands.guild_only() + @checks.admin_or_permissions(manage_channels=True) + async def ignore(self, ctx: commands.Context): + """Add servers or channels to the ignore list.""" + if ctx.invoked_subcommand is None: + for page in pagify(await self.count_ignored(ctx)): + await ctx.maybe_send_embed(page) + + @ignore.command(name="channel") + async def ignore_channel( + self, + ctx: commands.Context, + channel: Optional[Union[discord.TextChannel, discord.CategoryChannel]] = None, + ): + """Ignore commands in the channel or category. + + Defaults to the current channel. + """ + if not channel: + channel = ctx.channel + if not await self.bot._ignored_cache.get_ignored_channel(channel): + await self.bot._ignored_cache.set_ignored_channel(channel, True) + await ctx.send(_("Channel added to ignore list.")) + else: + await ctx.send(_("Channel already in ignore list.")) + + @ignore.command(name="server", aliases=["guild"]) + @checks.admin_or_permissions(manage_guild=True) + async def ignore_guild(self, ctx: commands.Context): + """Ignore commands in this server.""" + guild = ctx.guild + if not await self.bot._ignored_cache.get_ignored_guild(guild): + await self.bot._ignored_cache.set_ignored_guild(guild, True) + await ctx.send(_("This server has been added to the ignore list.")) + else: + await ctx.send(_("This server is already being ignored.")) + + @commands.group() + @commands.guild_only() + @checks.admin_or_permissions(manage_channels=True) + async def unignore(self, ctx: commands.Context): + """Remove servers or channels from the ignore list.""" + if ctx.invoked_subcommand is None: + for page in pagify(await self.count_ignored(ctx)): + await ctx.maybe_send_embed(page) + + @unignore.command(name="channel") + async def unignore_channel( + self, + ctx: commands.Context, + channel: Optional[Union[discord.TextChannel, discord.CategoryChannel]] = None, + ): + """Remove a channel or category from ignore the list. + + Defaults to the current channel. + """ + if not channel: + channel = ctx.channel + + if await self.bot._ignored_cache.get_ignored_channel(channel): + await self.bot._ignored_cache.set_ignored_channel(channel, False) + await ctx.send(_("Channel removed from ignore list.")) + else: + await ctx.send(_("That channel is not in the ignore list.")) + + @unignore.command(name="server", aliases=["guild"]) + @checks.admin_or_permissions(manage_guild=True) + async def unignore_guild(self, ctx: commands.Context): + """Remove this server from the ignore list.""" + guild = ctx.message.guild + if await self.bot._ignored_cache.get_ignored_guild(guild): + await self.bot._ignored_cache.set_ignored_guild(guild, False) + await ctx.send(_("This server has been removed from the ignore list.")) + else: + await ctx.send(_("This server is not in the ignore list.")) + + async def count_ignored(self, ctx: commands.Context): + category_channels: List[discord.CategoryChannel] = [] + text_channels: List[discord.TextChannel] = [] + if await self.bot._ignored_cache.get_ignored_guild(ctx.guild): + return _("This server is currently being ignored.") + for channel in ctx.guild.text_channels: + if channel.category and channel.category not in category_channels: + if await self.bot._ignored_cache.get_ignored_channel(channel.category): + category_channels.append(channel.category) + continue + else: + continue + if await self.bot._ignored_cache.get_ignored_channel(channel): + text_channels.append(channel) + + cat_str = ( + humanize_list([c.name for c in category_channels]) if category_channels else "None" + ) + chan_str = humanize_list([c.mention for c in text_channels]) if text_channels else "None" + msg = _("Currently ignored categories: {categories}\nChannels:{channels}").format( + categories=cat_str, channels=chan_str + ) + return msg + # Removing this command from forks is a violation of the GPLv3 under which it is licensed. # Otherwise interfering with the ability for this command to be accessible is also a violation. diff --git a/redbot/core/global_checks.py b/redbot/core/global_checks.py index e88405df63c..38dbf6f904f 100644 --- a/redbot/core/global_checks.py +++ b/redbot/core/global_checks.py @@ -7,7 +7,7 @@ def init_global_checks(bot): def minimum_bot_perms(ctx) -> bool: """ Too many 403, 401, and 429 Errors can cause bots to get global'd - + It's reasonable to assume the below as a minimum amount of perms for commands. """ @@ -17,6 +17,11 @@ def minimum_bot_perms(ctx) -> bool: async def whiteblacklist_checks(ctx) -> bool: return await ctx.bot.allowed_by_whitelist_blacklist(ctx.author) + @bot.check_once + async def ignore_checks(ctx) -> bool: + """Check the channel or server is not ignored""" + return await ctx.bot.ignored_channel_or_guild(ctx) + @bot.check_once def bots(ctx) -> bool: """Check the user is not another bot.""" diff --git a/redbot/core/settings_caches.py b/redbot/core/settings_caches.py index 32c4c5d520c..63d0de60637 100644 --- a/redbot/core/settings_caches.py +++ b/redbot/core/settings_caches.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Union from argparse import Namespace import discord @@ -51,3 +51,206 @@ async def set_prefixes( else: del self._cached[gid] await self._config.guild_from_id(gid).prefix.set(prefixes) + + +class IgnoreManager: + def __init__(self, config: Config): + self._config: Config = config + self._cached_channels: Dict[int, bool] = {} + self._cached_guilds: Dict[int, bool] = {} + + async def get_ignored_channel(self, channel: discord.TextChannel) -> bool: + ret: bool + + cid: int = channel.id + cat_id: Optional[int] = channel.category.id if channel.category else None + if cid in self._cached_channels: + chan_ret = self._cached_channels[cid] + else: + chan_ret = await self._config.channel_from_id(cid).ignored() + self._cached_channels[cid] = chan_ret + if cat_id and cat_id in self._cached_channels: + cat_ret = self._cached_channels[cat_id] + else: + if cat_id: + cat_ret = await self._config.channel_from_id(cat_id).ignored() + self._cached_channels[cat_id] = cat_ret + else: + cat_ret = False + ret = chan_ret or cat_ret + + return ret + + async def set_ignored_channel( + self, channel: Union[discord.TextChannel, discord.CategoryChannel], set_to: bool + ): + + cid: int = channel.id + self._cached_channels[cid] = set_to + if set_to: + await self._config.channel_from_id(cid).ignored.set(set_to) + else: + await self._config.channel_from_id(cid).ignored.clear() + + async def get_ignored_guild(self, guild: discord.Guild) -> bool: + ret: bool + + gid: int = guild.id + + if gid in self._cached_guilds: + ret = self._cached_guilds[gid] + else: + ret = await self._config.guild_from_id(gid).ignored() + self._cached_guilds[gid] = ret + + return ret + + async def set_ignored_guild(self, guild: discord.Guild, set_to: bool): + + gid: int = guild.id + self._cached_guilds[gid] = set_to + if set_to: + await self._config.guild_from_id(gid).ignored.set(set_to) + else: + await self._config.guild_from_id(gid).ignored.clear() + + +class WhitelistBlacklistManager: + def __init__(self, config: Config): + self._config: Config = config + self._cached_whitelist: Dict[Optional[int], List[int]] = {} + self._cached_blacklist: Dict[Optional[int], List[int]] = {} + + async def get_whitelist(self, guild: Optional[discord.Guild] = None) -> List[int]: + ret: List[int] + + gid: Optional[int] = guild.id if guild else None + + if gid in self._cached_whitelist: + ret = self._cached_whitelist[gid].copy() + else: + if gid is not None: + ret = await self._config.guild_from_id(gid).whitelsit() + if not ret: + ret = [] + else: + ret = await self._config.whitelist() + + self._cached_whitelist[gid] = ret.copy() + + return ret + + async def add_to_whitelist(self, guild: Optional[discord.Guild], role_or_user: List[int]): + gid: Optional[int] = guild.id if guild else None + role_or_user = role_or_user or [] + if not isinstance(role_or_user, list) and not all( + isinstance(r_or_u, str) for r_or_u in role_or_user + ): + raise TypeError("Whitelisted objects must be a list of ints") + if gid is None: + for obj_id in role_or_user: + if obj_id not in self._cached_whitelist: + self._cached_whitelist[gid].append(obj_id) + async with self._config.whitelist() as curr_list: + curr_list.append(obj_id) + else: + for obj_id in role_or_user: + if obj_id not in self._cached_whitelist: + self._cached_whitelist[gid].append(obj_id) + async with self._config.guild_from_id(gid).whitelist() as curr_list: + curr_list.append(obj_id) + + async def clear_whitelist(self, guild: Optional[discord.Guild] = None): + gid: Optional[int] = guild.id if guild else None + self._cached_whitelist[gid] + if gid is None: + await self._config.whitelist.clear() + else: + await self._config.guild_from_id(gid).whitelist.clear() + + async def remove_from_whitelist(self, guild: Optional[discord.Guild], role_or_user: List[int]): + gid: Optional[int] = guild.id if guild else None + role_or_user = role_or_user or [] + if not isinstance(role_or_user, list) and not all( + isinstance(r_or_u, str) for r_or_u in role_or_user + ): + raise TypeError("Whitelisted objects must be a list of ints") + if gid is None: + for obj_id in role_or_user: + if obj_id in self._cached_whitelist: + self._cached_whitelist[gid].remove(obj_id) + async with self._config.whitelist() as curr_list: + curr_list.remove(obj_id) + else: + for obj_id in role_or_user: + if obj_id not in self._cached_whitelist: + self._cached_whitelist[gid].remove(obj_id) + async with self._config.guild_from_id(gid).whitelist() as curr_list: + curr_list.remove(obj_id) + + async def get_blacklist(self, guild: Optional[discord.Guild] = None) -> List[int]: + ret: List[int] + + gid: Optional[int] = guild.id if guild else None + + if gid in self._cached_blacklist: + ret = self._cached_blacklist[gid].copy() + else: + if gid is not None: + ret = await self._config.guild_from_id(gid).whitelsit() + if not ret: + ret = [] + else: + ret = await self._config.blacklist() + + self._cached_blacklist[gid] = ret.copy() + + return ret + + async def add_to_blacklist(self, guild: Optional[discord.Guild], role_or_user: List[int]): + gid: Optional[int] = guild.id if guild else None + role_or_user = role_or_user or [] + if not isinstance(role_or_user, list) and not all( + isinstance(r_or_u, str) for r_or_u in role_or_user + ): + raise TypeError("Blacklisted objects must be a list of ints") + if gid is None: + for obj_id in role_or_user: + if obj_id not in self._cached_blacklist: + self._cached_blacklist[gid].append(obj_id) + async with self._config.blacklist() as curr_list: + curr_list.append(obj_id) + else: + for obj_id in role_or_user: + if obj_id not in self._cached_blacklist: + self._cached_blacklist[gid].append(obj_id) + async with self._config.guild_from_id(gid).blacklist() as curr_list: + curr_list.append(obj_id) + + async def clear_blacklist(self, guild: Optional[discord.Guild] = None): + gid: Optional[int] = guild.id if guild else None + self._cached_whitelist[gid] + if gid is None: + await self._config.blacklist.clear() + else: + await self._config.guild_from_id(gid).blacklist.clear() + + async def remove_from_blacklist(self, guild: Optional[discord.Guild], role_or_user: List[int]): + gid: Optional[int] = guild.id if guild else None + role_or_user = role_or_user or [] + if not isinstance(role_or_user, list) and not all( + isinstance(r_or_u, str) for r_or_u in role_or_user + ): + raise TypeError("Blacklisted objects must be a list of ints") + if gid is None: + for obj_id in role_or_user: + if obj_id in self._cached_blacklist: + self._cached_blacklist[gid].remove(obj_id) + async with self._config.blacklist() as curr_list: + curr_list.remove(obj_id) + else: + for obj_id in role_or_user: + if obj_id not in self._cached_blacklist: + self._cached_blacklist[gid].remove(obj_id) + async with self._config.guild_from_id(gid).blacklist() as curr_list: + curr_list.remove(obj_id)