mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-21 02:16:09 -05:00
Begin work on a data request API (#4045)
[Core] Data Deletion And Disclosure APIs - Adds a Data Deletion API - Deletion comes in a few forms based on who is requesting - Deletion must be handled by 3rd party - Adds a Data Collection Disclosure Command - Provides a dynamically generated statement from 3rd party extensions - Modifies the always available commands to be cog compatible - Also prevents them from being unloaded accidentally
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, List, Optional, Union, Set, Iterable, Tuple
|
||||
import asyncio
|
||||
from argparse import Namespace
|
||||
from collections import defaultdict
|
||||
|
||||
import discord
|
||||
|
||||
from .config import Config
|
||||
from .utils import AsyncIter
|
||||
|
||||
|
||||
class PrefixManager:
|
||||
@@ -125,136 +127,185 @@ class WhitelistBlacklistManager:
|
||||
self._config: Config = config
|
||||
self._cached_whitelist: Dict[Optional[int], Set[int]] = {}
|
||||
self._cached_blacklist: Dict[Optional[int], Set[int]] = {}
|
||||
# because of discord deletion
|
||||
# we now have sync and async access that may need to happen at the
|
||||
# same time.
|
||||
# blame discord for this.
|
||||
self._access_lock = asyncio.Lock()
|
||||
|
||||
async def discord_deleted_user(self, user_id: int):
|
||||
|
||||
async with self._access_lock:
|
||||
|
||||
async for guild_id_or_none, ids in AsyncIter(
|
||||
self._cached_whitelist.items(), steps=100
|
||||
):
|
||||
ids.discard(user_id)
|
||||
|
||||
async for guild_id_or_none, ids in AsyncIter(
|
||||
self._cached_blacklist.items(), steps=100
|
||||
):
|
||||
ids.discard(user_id)
|
||||
|
||||
for grp in (self._config.whitelist, self._config.blacklist):
|
||||
async with grp() as ul:
|
||||
try:
|
||||
ul.remove(user_id)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# don't use this in extensions, it's optimized and controlled for here,
|
||||
# but can't be safe in 3rd party use
|
||||
|
||||
async with self._config._get_base_group("GUILD").all() as abuse:
|
||||
for guild_str, guild_data in abuse.items():
|
||||
for l_name in ("whitelist", "blacklist"):
|
||||
try:
|
||||
guild_data[l_name].remove(user_id)
|
||||
except (ValueError, KeyError):
|
||||
pass # this is raw access not filled with defaults
|
||||
|
||||
async def get_whitelist(self, guild: Optional[discord.Guild] = None) -> Set[int]:
|
||||
ret: Set[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 = set(await self._config.guild_from_id(gid).whitelist())
|
||||
async with self._access_lock:
|
||||
ret: Set[int]
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
if gid in self._cached_whitelist:
|
||||
ret = self._cached_whitelist[gid].copy()
|
||||
else:
|
||||
ret = set(await self._config.whitelist())
|
||||
if gid is not None:
|
||||
ret = set(await self._config.guild_from_id(gid).whitelist())
|
||||
else:
|
||||
ret = set(await self._config.whitelist())
|
||||
|
||||
self._cached_whitelist[gid] = ret.copy()
|
||||
self._cached_whitelist[gid] = ret.copy()
|
||||
|
||||
return ret
|
||||
return ret
|
||||
|
||||
async def add_to_whitelist(self, guild: Optional[discord.Guild], role_or_user: Iterable[int]):
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
role_or_user = role_or_user or []
|
||||
if not all(isinstance(r_or_u, int) for r_or_u in role_or_user):
|
||||
raise TypeError("`role_or_user` must be an iterable of `int`s.")
|
||||
async with self._access_lock:
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
role_or_user = role_or_user or []
|
||||
if not all(isinstance(r_or_u, int) for r_or_u in role_or_user):
|
||||
raise TypeError("`role_or_user` must be an iterable of `int`s.")
|
||||
|
||||
if gid is None:
|
||||
if gid not in self._cached_whitelist:
|
||||
self._cached_whitelist[gid] = set(await self._config.whitelist())
|
||||
self._cached_whitelist[gid].update(role_or_user)
|
||||
await self._config.whitelist.set(list(self._cached_whitelist[gid]))
|
||||
if gid is None:
|
||||
if gid not in self._cached_whitelist:
|
||||
self._cached_whitelist[gid] = set(await self._config.whitelist())
|
||||
self._cached_whitelist[gid].update(role_or_user)
|
||||
await self._config.whitelist.set(list(self._cached_whitelist[gid]))
|
||||
|
||||
else:
|
||||
if gid not in self._cached_whitelist:
|
||||
self._cached_whitelist[gid] = set(
|
||||
await self._config.guild_from_id(gid).whitelist()
|
||||
else:
|
||||
if gid not in self._cached_whitelist:
|
||||
self._cached_whitelist[gid] = set(
|
||||
await self._config.guild_from_id(gid).whitelist()
|
||||
)
|
||||
self._cached_whitelist[gid].update(role_or_user)
|
||||
await self._config.guild_from_id(gid).whitelist.set(
|
||||
list(self._cached_whitelist[gid])
|
||||
)
|
||||
self._cached_whitelist[gid].update(role_or_user)
|
||||
await self._config.guild_from_id(gid).whitelist.set(list(self._cached_whitelist[gid]))
|
||||
|
||||
async def clear_whitelist(self, guild: Optional[discord.Guild] = None):
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
self._cached_whitelist[gid] = set()
|
||||
if gid is None:
|
||||
await self._config.whitelist.clear()
|
||||
else:
|
||||
await self._config.guild_from_id(gid).whitelist.clear()
|
||||
async with self._access_lock:
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
self._cached_whitelist[gid] = set()
|
||||
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: Iterable[int]
|
||||
):
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
role_or_user = role_or_user or []
|
||||
if not all(isinstance(r_or_u, int) for r_or_u in role_or_user):
|
||||
raise TypeError("`role_or_user` must be an iterable of `int`s.")
|
||||
async with self._access_lock:
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
role_or_user = role_or_user or []
|
||||
if not all(isinstance(r_or_u, int) for r_or_u in role_or_user):
|
||||
raise TypeError("`role_or_user` must be an iterable of `int`s.")
|
||||
|
||||
if gid is None:
|
||||
if gid not in self._cached_whitelist:
|
||||
self._cached_whitelist[gid] = set(await self._config.whitelist())
|
||||
self._cached_whitelist[gid].difference_update(role_or_user)
|
||||
await self._config.whitelist.set(list(self._cached_whitelist[gid]))
|
||||
if gid is None:
|
||||
if gid not in self._cached_whitelist:
|
||||
self._cached_whitelist[gid] = set(await self._config.whitelist())
|
||||
self._cached_whitelist[gid].difference_update(role_or_user)
|
||||
await self._config.whitelist.set(list(self._cached_whitelist[gid]))
|
||||
|
||||
else:
|
||||
if gid not in self._cached_whitelist:
|
||||
self._cached_whitelist[gid] = set(
|
||||
await self._config.guild_from_id(gid).whitelist()
|
||||
else:
|
||||
if gid not in self._cached_whitelist:
|
||||
self._cached_whitelist[gid] = set(
|
||||
await self._config.guild_from_id(gid).whitelist()
|
||||
)
|
||||
self._cached_whitelist[gid].difference_update(role_or_user)
|
||||
await self._config.guild_from_id(gid).whitelist.set(
|
||||
list(self._cached_whitelist[gid])
|
||||
)
|
||||
self._cached_whitelist[gid].difference_update(role_or_user)
|
||||
await self._config.guild_from_id(gid).whitelist.set(list(self._cached_whitelist[gid]))
|
||||
|
||||
async def get_blacklist(self, guild: Optional[discord.Guild] = None) -> Set[int]:
|
||||
ret: Set[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 = set(await self._config.guild_from_id(gid).blacklist())
|
||||
async with self._access_lock:
|
||||
ret: Set[int]
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
if gid in self._cached_blacklist:
|
||||
ret = self._cached_blacklist[gid].copy()
|
||||
else:
|
||||
ret = set(await self._config.blacklist())
|
||||
if gid is not None:
|
||||
ret = set(await self._config.guild_from_id(gid).blacklist())
|
||||
else:
|
||||
ret = set(await self._config.blacklist())
|
||||
|
||||
self._cached_blacklist[gid] = ret.copy()
|
||||
self._cached_blacklist[gid] = ret.copy()
|
||||
|
||||
return ret
|
||||
return ret
|
||||
|
||||
async def add_to_blacklist(self, guild: Optional[discord.Guild], role_or_user: Iterable[int]):
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
role_or_user = role_or_user or []
|
||||
if not all(isinstance(r_or_u, int) for r_or_u in role_or_user):
|
||||
raise TypeError("`role_or_user` must be an iterable of `int`s.")
|
||||
if gid is None:
|
||||
if gid not in self._cached_blacklist:
|
||||
self._cached_blacklist[gid] = set(await self._config.blacklist())
|
||||
self._cached_blacklist[gid].update(role_or_user)
|
||||
await self._config.blacklist.set(list(self._cached_blacklist[gid]))
|
||||
else:
|
||||
if gid not in self._cached_blacklist:
|
||||
self._cached_blacklist[gid] = set(
|
||||
await self._config.guild_from_id(gid).blacklist()
|
||||
async with self._access_lock:
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
role_or_user = role_or_user or []
|
||||
if not all(isinstance(r_or_u, int) for r_or_u in role_or_user):
|
||||
raise TypeError("`role_or_user` must be an iterable of `int`s.")
|
||||
if gid is None:
|
||||
if gid not in self._cached_blacklist:
|
||||
self._cached_blacklist[gid] = set(await self._config.blacklist())
|
||||
self._cached_blacklist[gid].update(role_or_user)
|
||||
await self._config.blacklist.set(list(self._cached_blacklist[gid]))
|
||||
else:
|
||||
if gid not in self._cached_blacklist:
|
||||
self._cached_blacklist[gid] = set(
|
||||
await self._config.guild_from_id(gid).blacklist()
|
||||
)
|
||||
self._cached_blacklist[gid].update(role_or_user)
|
||||
await self._config.guild_from_id(gid).blacklist.set(
|
||||
list(self._cached_blacklist[gid])
|
||||
)
|
||||
self._cached_blacklist[gid].update(role_or_user)
|
||||
await self._config.guild_from_id(gid).blacklist.set(list(self._cached_blacklist[gid]))
|
||||
|
||||
async def clear_blacklist(self, guild: Optional[discord.Guild] = None):
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
self._cached_blacklist[gid] = set()
|
||||
if gid is None:
|
||||
await self._config.blacklist.clear()
|
||||
else:
|
||||
await self._config.guild_from_id(gid).blacklist.clear()
|
||||
async with self._access_lock:
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
self._cached_blacklist[gid] = set()
|
||||
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: Iterable[int]
|
||||
):
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
role_or_user = role_or_user or []
|
||||
if not all(isinstance(r_or_u, int) for r_or_u in role_or_user):
|
||||
raise TypeError("`role_or_user` must be an iterable of `int`s.")
|
||||
if gid is None:
|
||||
if gid not in self._cached_blacklist:
|
||||
self._cached_blacklist[gid] = set(await self._config.blacklist())
|
||||
self._cached_blacklist[gid].difference_update(role_or_user)
|
||||
await self._config.blacklist.set(list(self._cached_blacklist[gid]))
|
||||
else:
|
||||
if gid not in self._cached_blacklist:
|
||||
self._cached_blacklist[gid] = set(
|
||||
await self._config.guild_from_id(gid).blacklist()
|
||||
async with self._access_lock:
|
||||
gid: Optional[int] = guild.id if guild else None
|
||||
role_or_user = role_or_user or []
|
||||
if not all(isinstance(r_or_u, int) for r_or_u in role_or_user):
|
||||
raise TypeError("`role_or_user` must be an iterable of `int`s.")
|
||||
if gid is None:
|
||||
if gid not in self._cached_blacklist:
|
||||
self._cached_blacklist[gid] = set(await self._config.blacklist())
|
||||
self._cached_blacklist[gid].difference_update(role_or_user)
|
||||
await self._config.blacklist.set(list(self._cached_blacklist[gid]))
|
||||
else:
|
||||
if gid not in self._cached_blacklist:
|
||||
self._cached_blacklist[gid] = set(
|
||||
await self._config.guild_from_id(gid).blacklist()
|
||||
)
|
||||
self._cached_blacklist[gid].difference_update(role_or_user)
|
||||
await self._config.guild_from_id(gid).blacklist.set(
|
||||
list(self._cached_blacklist[gid])
|
||||
)
|
||||
self._cached_blacklist[gid].difference_update(role_or_user)
|
||||
await self._config.guild_from_id(gid).blacklist.set(list(self._cached_blacklist[gid]))
|
||||
|
||||
|
||||
class DisabledCogCache:
|
||||
|
||||
Reference in New Issue
Block a user