Repository: Cog-Creators/Red-DiscordBot

Issue:
further API support for the blacklist manager.
### Type of feature request

API functionality

### Description of the feature you're suggesting

Hello, I wanted to request API functionality for Red's private blacklist manager.

Either that or some public method(s) to allow cog creators access to manually add and/or remove users from the bot's blacklist. This could be useful if someone is intrigued to change the functionality of the inbuilt blacklist command (like me and jojo). This would allow us to access the blacklist and modify it as per our use case and leisure.

### Anything else?

_No response_


Discussion:

Resolving patch:
diff --git a/redbot/core/_diagnoser.py b/redbot/core/_diagnoser.py
--- a/redbot/core/_diagnoser.py
+++ b/redbot/core/_diagnoser.py
@@ -166,7 +166,7 @@ async def _check_ignored_issues(self) -> CheckResult:
         )
 
     async def _get_detailed_global_whitelist_blacklist_result(self, label: str) -> CheckResult:
-        global_whitelist = await self.bot._whiteblacklist_cache.get_whitelist()
+        global_whitelist = await self.bot.get_whitelist()
         if global_whitelist:
             return CheckResult(
                 False,
@@ -206,7 +206,7 @@ async def _get_detailed_global_whitelist_blacklist_result(self, label: str) -> C
     async def _get_detailed_local_whitelist_blacklist_result(self, label: str) -> CheckResult:
         # this method skips guild owner check as the earlier checks wouldn't fail
         # if the user were guild owner
-        guild_whitelist = await self.bot._whiteblacklist_cache.get_whitelist(self.guild)
+        guild_whitelist = await self.bot.get_whitelist(self.guild)
         if guild_whitelist:
             return CheckResult(
                 False,
@@ -227,7 +227,7 @@ async def _get_detailed_local_whitelist_blacklist_result(self, label: str) -> Ch
             )
 
         details = _("Local blocklist prevents the user from running this command.")
-        guild_blacklist = await self.bot._whiteblacklist_cache.get_blacklist(self.guild)
+        guild_blacklist = await self.bot.get_blacklist(self.guild)
         ids = {role.id for role in self.author.roles if not role.is_default()}
         ids.add(self.author.id)
         intersection = ids & guild_blacklist
diff --git a/redbot/core/bot.py b/redbot/core/bot.py
--- a/redbot/core/bot.py
+++ b/redbot/core/bot.py
@@ -17,6 +17,7 @@
     Optional,
     Union,
     List,
+    Iterable,
     Dict,
     NoReturn,
     Set,
@@ -26,6 +27,7 @@
     Any,
     Literal,
     MutableMapping,
+    Set,
     overload,
 )
 from types import MappingProxyType
@@ -66,6 +68,7 @@
 
 PreInvokeCoroutine = Callable[[commands.Context], Awaitable[Any]]
 T_BIC = TypeVar("T_BIC", bound=PreInvokeCoroutine)
+UserOrRole = Union[int, discord.Role, discord.Member, discord.User]
 
 
 def _is_submodule(parent, child):
@@ -523,6 +526,156 @@ def colour(self) -> NoReturn:
     def max_messages(self) -> Optional[int]:
         return self._max_messages
 
+    async def add_to_blacklist(
+        self, users_or_roles: Iterable[UserOrRole], *, guild: Optional[discord.Guild] = None
+    ):
+        """
+        Add users or roles to the global or local blocklist.
+
+        Parameters
+        ----------
+        users_or_roles : Iterable[Union[int, discord.Role, discord.Member, discord.User]]
+            The items to add to the blocklist.
+            Roles and role IDs should only be passed when updating a local blocklist.
+        guild : Optional[discord.Guild]
+            The guild, whose local blocklist should be modified.
+            If not passed, the global blocklist will be modified.
+
+        Raises
+        ------
+        TypeError
+            The values passed were not of the proper type.
+        """
+        to_add: Set[int] = {getattr(uor, "id", uor) for uor in users_or_roles}
+        await self._whiteblacklist_cache.add_to_blacklist(guild, to_add)
+
+    async def remove_from_blacklist(
+        self, users_or_roles: Iterable[UserOrRole], *, guild: Optional[discord.Guild] = None
+    ):
+        """
+        Remove users or roles from the global or local blocklist.
+
+        Parameters
+        ----------
+        users_or_roles : Iterable[Union[int, discord.Role, discord.Member, discord.User]]
+            The items to remove from the blocklist.
+            Roles and role IDs should only be passed when updating a local blocklist.
+        guild : Optional[discord.Guild]
+            The guild, whose local blocklist should be modified.
+            If not passed, the global blocklist will be modified.
+
+        Raises
+        ------
+        TypeError
+            The values passed were not of the proper type.
+        """
+        to_remove: Set[int] = {getattr(uor, "id", uor) for uor in users_or_roles}
+        await self._whiteblacklist_cache.remove_from_blacklist(guild, to_remove)
+
+    async def get_blacklist(self, guild: Optional[discord.Guild] = None) -> Set[int]:
+        """
+        Get the global or local blocklist.
+
+        Parameters
+        ----------
+        guild : Optional[discord.Guild]
+            The guild to get the local blocklist for.
+            If this is not passed, the global blocklist will be returned.
+
+        Returns
+        -------
+        Set[int]
+            The IDs of the blocked users/roles.
+        """
+        return await self._whiteblacklist_cache.get_blacklist(guild)
+
+    async def clear_blacklist(self, guild: Optional[discord.Guild] = None):
+        """
+        Clears the global or local blocklist.
+
+        Parameters
+        ----------
+        guild : Optional[discord.Guild]
+            The guild, whose local blocklist should be cleared.
+            If not passed, the global blocklist will be cleared.
+        """
+        await self._whiteblacklist_cache.clear_blacklist(guild)
+
+    async def add_to_whitelist(
+        self, users_or_roles: Iterable[UserOrRole], *, guild: Optional[discord.Guild] = None
+    ):
+        """
+        Add users or roles to the global or local allowlist.
+
+        Parameters
+        ----------
+        users_or_roles : Iterable[Union[int, discord.Role, discord.Member, discord.User]]
+            The items to add to the allowlist.
+            Roles and role IDs should only be passed when updating a local allowlist.
+        guild : Optional[discord.Guild]
+            The guild, whose local allowlist should be modified.
+            If not passed, the global allowlist will be modified.
+
+        Raises
+        ------
+        TypeError
+            The passed values were not of the proper type.
+        """
+        to_add: Set[int] = {getattr(uor, "id", uor) for uor in users_or_roles}
+        await self._whiteblacklist_cache.add_to_whitelist(guild, to_add)
+
+    async def remove_from_whitelist(
+        self, users_or_roles: Iterable[UserOrRole], *, guild: Optional[discord.Guild] = None
+    ):
+        """
+        Remove users or roles from the global or local allowlist.
+
+        Parameters
+        ----------
+        users_or_roles : Iterable[Union[int, discord.Role, discord.Member, discord.User]]
+            The items to remove from the allowlist.
+            Roles and role IDs should only be passed when updating a local allowlist.
+        guild : Optional[discord.Guild]
+            The guild, whose local allowlist should be modified.
+            If not passed, the global allowlist will be modified.
+
+        Raises
+        ------
+        TypeError
+            The passed values were not of the proper type.
+        """
+        to_remove: Set[int] = {getattr(uor, "id", uor) for uor in users_or_roles}
+        await self._whiteblacklist_cache.remove_from_whitelist(guild, to_remove)
+
+    async def get_whitelist(self, guild: Optional[discord.Guild] = None):
+        """
+        Get the global or local allowlist.
+
+        Parameters
+        ----------
+        guild : Optional[discord.Guild]
+            The guild to get the local allowlist for.
+            If this is not passed, the global allowlist will be returned.
+
+        Returns
+        -------
+        Set[int]
+            The IDs of the allowed users/roles.
+        """
+        return await self._whiteblacklist_cache.get_whitelist(guild)
+
+    async def clear_whitelist(self, guild: Optional[discord.Guild] = None):
+        """
+        Clears the global or local allowlist.
+
+        Parameters
+        ----------
+        guild : Optional[discord.Guild]
+            The guild, whose local allowlist should be cleared.
+            If not passed, the global allowlist will be cleared.
+        """
+        await self._whiteblacklist_cache.clear_whitelist(guild)
+
     async def allowed_by_whitelist_blacklist(
         self,
         who: Optional[Union[discord.Member, discord.User]] = None,
@@ -534,7 +687,7 @@ async def allowed_by_whitelist_blacklist(
     ) -> bool:
         """
         This checks if a user or member is allowed to run things,
-        as considered by Red's whitelist and blacklist.
+        as considered by Red's allowlist and blocklist.
 
         If given a user object, this function will check the global lists
 
@@ -614,13 +767,13 @@ async def allowed_by_whitelist_blacklist(
         if await self.is_owner(who):
             return True
 
-        global_whitelist = await self._whiteblacklist_cache.get_whitelist()
+        global_whitelist = await self.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._whiteblacklist_cache.get_blacklist()
+            global_blacklist = await self.get_blacklist()
             if who.id in global_blacklist:
                 return False
 
@@ -648,12 +801,12 @@ 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._whiteblacklist_cache.get_whitelist(guild)
+            guild_whitelist = await self.get_whitelist(guild)
             if guild_whitelist:
                 if ids.isdisjoint(guild_whitelist):
                     return False
             else:
-                guild_blacklist = await self._whiteblacklist_cache.get_blacklist(guild)
+                guild_blacklist = await self.get_blacklist(guild)
                 if not ids.isdisjoint(guild_blacklist):
                     return False
 
diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py
--- a/redbot/core/core_commands.py
+++ b/redbot/core/core_commands.py
@@ -3765,9 +3765,8 @@ async def allowlist_add(self, ctx: commands.Context, *users: Union[discord.Membe
         **Arguments:**
             - `<users...>` - The user or users to add to the allowlist.
         """
-        uids = {getattr(user, "id", user) for user in users}
-        await self.bot._whiteblacklist_cache.add_to_whitelist(None, uids)
-        if len(uids) > 1:
+        await self.bot.add_to_whitelist(users)
+        if len(users) > 1:
             await ctx.send(_("Users have been added to the allowlist."))
         else:
             await ctx.send(_("User has been added to the allowlist."))
@@ -3812,9 +3811,8 @@ async def allowlist_remove(self, ctx: commands.Context, *users: Union[discord.Me
         **Arguments:**
             - `<users...>` - The user or users to remove from the allowlist.
         """
-        uids = {getattr(user, "id", user) for user in users}
-        await self.bot._whiteblacklist_cache.remove_from_whitelist(None, uids)
-        if len(uids) > 1:
+        await self.bot.remove_from_whitelist(users)
+        if len(users) > 1:
             await ctx.send(_("Users have been removed from the allowlist."))
         else:
             await ctx.send(_("User has been removed from the allowlist."))
@@ -3829,7 +3827,7 @@ async def allowlist_clear(self, ctx: commands.Context):
         **Example:**
             - `[p]allowlist clear`
         """
-        await self.bot._whiteblacklist_cache.clear_whitelist()
+        await self.bot.clear_whitelist()
         await ctx.send(_("Allowlist has been cleared."))
 
     @commands.group(aliases=["blacklist", "denylist"])
@@ -3863,9 +3861,8 @@ async def blocklist_add(self, ctx: commands.Context, *users: Union[discord.Membe
                 await ctx.send(_("You cannot add an owner to the blocklist!"))
                 return
 
-        uids = {getattr(user, "id", user) for user in users}
-        await self.bot._whiteblacklist_cache.add_to_blacklist(None, uids)
-        if len(uids) > 1:
+        await self.bot.add_to_blacklist(users)
+        if len(users) > 1:
             await ctx.send(_("Users have been added to the blocklist."))
         else:
             await ctx.send(_("User has been added to the blocklist."))
@@ -3878,7 +3875,7 @@ async def blocklist_list(self, ctx: commands.Context):
         **Example:**
             - `[p]blocklist list`
         """
-        curr_list = await self.bot._whiteblacklist_cache.get_blacklist(None)
+        curr_list = await self.bot.get_blacklist()
 
         if not curr_list:
             await ctx.send("Blocklist is empty.")
@@ -3908,9 +3905,8 @@ async def blocklist_remove(self, ctx: commands.Context, *users: Union[discord.Me
         **Arguments:**
             - `<users...>` - The user or users to remove from the blocklist.
         """
-        uids = {getattr(user, "id", user) for user in users}
-        await self.bot._whiteblacklist_cache.remove_from_blacklist(None, uids)
-        if len(uids) > 1:
+        await self.bot.remove_from_blacklist(users)
+        if len(users) > 1:
             await ctx.send(_("Users have been removed from the blocklist."))
         else:
             await ctx.send(_("User has been removed from the blocklist."))
@@ -3923,7 +3919,7 @@ async def blocklist_clear(self, ctx: commands.Context):
         **Example:**
             - `[p]blocklist clear`
         """
-        await self.bot._whiteblacklist_cache.clear_blacklist()
+        await self.bot.clear_blacklist()
         await ctx.send(_("Blocklist has been cleared."))
 
     @commands.group(aliases=["localwhitelist"])
@@ -3957,7 +3953,7 @@ async def localallowlist_add(
         names = [getattr(u_or_r, "name", u_or_r) for u_or_r in users_or_roles]
         uids = {getattr(u_or_r, "id", u_or_r) for u_or_r in users_or_roles}
         if not (ctx.guild.owner == ctx.author or await self.bot.is_owner(ctx.author)):
-            current_whitelist = await self.bot._whiteblacklist_cache.get_whitelist(ctx.guild)
+            current_whitelist = await self.bot.get_whitelist(ctx.guild)
             theoretical_whitelist = current_whitelist.union(uids)
             ids = {i for i in (ctx.author.id, *(getattr(ctx.author, "_roles", [])))}
             if ids.isdisjoint(theoretical_whitelist):
@@ -3968,7 +3964,7 @@ async def localallowlist_add(
                         "please ensure to add yourself to the allowlist first."
                     )
                 )
-        await self.bot._whiteblacklist_cache.add_to_whitelist(ctx.guild, uids)
+        await self.bot.add_to_whitelist(uids, guild=ctx.guild)
 
         if len(uids) > 1:
             await ctx.send(_("Users and/or roles have been added to the allowlist."))
@@ -3983,7 +3979,7 @@ async def localallowlist_list(self, ctx: commands.Context):
         **Example:**
             - `[p]localallowlist list`
         """
-        curr_list = await self.bot._whiteblacklist_cache.get_whitelist(ctx.guild)
+        curr_list = await self.bot.get_whitelist(ctx.guild)
 
         if not curr_list:
             await ctx.send("Server allowlist is empty.")
@@ -4021,7 +4017,7 @@ async def localallowlist_remove(
         names = [getattr(u_or_r, "name", u_or_r) for u_or_r in users_or_roles]
         uids = {getattr(u_or_r, "id", u_or_r) for u_or_r in users_or_roles}
         if not (ctx.guild.owner == ctx.author or await self.bot.is_owner(ctx.author)):
-            current_whitelist = await self.bot._whiteblacklist_cache.get_whitelist(ctx.guild)
+            current_whitelist = await self.bot.get_whitelist(ctx.guild)
             theoretical_whitelist = current_whitelist - uids
             ids = {i for i in (ctx.author.id, *(getattr(ctx.author, "_roles", [])))}
             if theoretical_whitelist and ids.isdisjoint(theoretical_whitelist):
@@ -4031,7 +4027,7 @@ async def localallowlist_remove(
                         "remove your ability to run commands."
                     )
                 )
-        await self.bot._whiteblacklist_cache.remove_from_whitelist(ctx.guild, uids)
+        await self.bot.remove_from_whitelist(uids, guild=ctx.guild)
 
         if len(uids) > 1:
             await ctx.send(_("Users and/or roles have been removed from the server allowlist."))
@@ -4048,7 +4044,7 @@ async def localallowlist_clear(self, ctx: commands.Context):
         **Example:**
             - `[p]localallowlist clear`
         """
-        await self.bot._whiteblacklist_cache.clear_whitelist(ctx.guild)
+        await self.bot.clear_whitelist(ctx.guild)
         await ctx.send(_("Server allowlist has been cleared."))
 
     @commands.group(aliases=["localblacklist"])
@@ -4088,11 +4084,9 @@ async def localblocklist_add(
             if await ctx.bot.is_owner(uid):
                 await ctx.send(_("You cannot add a bot owner to the blocklist!"))
                 return
-        names = [getattr(u_or_r, "name", u_or_r) for u_or_r in users_or_roles]
-        uids = {getattr(u_or_r, "id", u_or_r) for u_or_r in users_or_roles}
-        await self.bot._whiteblacklist_cache.add_to_blacklist(ctx.guild, uids)
+        await self.bot.add_to_blacklist(users_or_roles, guild=ctx.guild)
 
-        if len(uids) > 1:
+        if len(users_or_roles) > 1:
             await ctx.send(_("Users and/or roles have been added from the server blocklist."))
         else:
             await ctx.send(_("User or role has been added from the server blocklist."))
@@ -4105,7 +4099,7 @@ async def localblocklist_list(self, ctx: commands.Context):
         **Example:**
             - `[p]localblocklist list`
         """
-        curr_list = await self.bot._whiteblacklist_cache.get_blacklist(ctx.guild)
+        curr_list = await self.bot.get_blacklist(ctx.guild)
 
         if not curr_list:
             await ctx.send("Server blocklist is empty.")
@@ -4138,11 +4132,9 @@ async def localblocklist_remove(
         **Arguments:**
             - `<users_or_roles...>` - The users or roles to remove from the local blocklist.
         """
-        names = [getattr(u_or_r, "name", u_or_r) for u_or_r in users_or_roles]
-        uids = {getattr(u_or_r, "id", u_or_r) for u_or_r in users_or_roles}
-        await self.bot._whiteblacklist_cache.remove_from_blacklist(ctx.guild, uids)
+        await self.bot.remove_from_blacklist(users_or_roles, guild=ctx.guild)
 
-        if len(uids) > 1:
+        if len(users_or_roles) > 1:
             await ctx.send(_("Users and/or roles have been removed from the server blocklist."))
         else:
             await ctx.send(_("User or role has been removed from the server blocklist."))
@@ -4157,7 +4149,7 @@ async def localblocklist_clear(self, ctx: commands.Context):
         **Example:**
             - `[p]blocklist clear`
         """
-        await self.bot._whiteblacklist_cache.clear_blacklist(ctx.guild)
+        await self.bot.clear_blacklist(ctx.guild)
         await ctx.send(_("Server blocklist has been cleared."))
 
     @checks.guildowner_or_permissions(administrator=True)


Test patch: