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:
Michael H
2020-08-03 09:09:07 -04:00
committed by GitHub
parent bb1a256295
commit c0b1e50a5f
38 changed files with 1761 additions and 222 deletions

View File

@@ -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: