mirror of https://gitlab.com/litecord/litecord.git
Merge branch 'impl/lazy-guild-removals' into 'master'
Impl/lazy guild removals Closes #93 See merge request litecord/litecord!75
This commit is contained in:
commit
a664110d2c
|
|
@ -199,6 +199,7 @@ async def modify_guild_member(guild_id, member_id):
|
||||||
return "", 204
|
return "", 204
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/<int:guild_id>/members/@me", methods=["PATCH"])
|
||||||
@bp.route("/<int:guild_id>/members/@me/nick", methods=["PATCH"])
|
@bp.route("/<int:guild_id>/members/@me/nick", methods=["PATCH"])
|
||||||
async def update_nickname(guild_id):
|
async def update_nickname(guild_id):
|
||||||
"""Update a member's nickname in a guild."""
|
"""Update a member's nickname in a guild."""
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,8 @@ async def create_guild():
|
||||||
else:
|
else:
|
||||||
image = None
|
image = None
|
||||||
|
|
||||||
|
region = j["region"] if "region" in j else next(iter(app.voice.lvsp.regions))
|
||||||
|
|
||||||
await app.db.execute(
|
await app.db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO guilds (id, name, region, icon, owner_id,
|
INSERT INTO guilds (id, name, region, icon, owner_id,
|
||||||
|
|
@ -136,7 +138,7 @@ async def create_guild():
|
||||||
""",
|
""",
|
||||||
guild_id,
|
guild_id,
|
||||||
j["name"],
|
j["name"],
|
||||||
j["region"],
|
region,
|
||||||
image,
|
image,
|
||||||
user_id,
|
user_id,
|
||||||
j.get("verification_level", 0),
|
j.get("verification_level", 0),
|
||||||
|
|
|
||||||
|
|
@ -329,7 +329,12 @@ async def add_member(guild_id: int, user_id: int, *, basic=False):
|
||||||
|
|
||||||
# pubsub changes for new member
|
# pubsub changes for new member
|
||||||
await app.lazy_guild.new_member(guild_id, user_id)
|
await app.lazy_guild.new_member(guild_id, user_id)
|
||||||
states = await app.dispatcher.guild.sub_user(guild_id, user_id)
|
|
||||||
|
# TODO how to remove repetition between this and websocket's subscribe_all?
|
||||||
|
states, channels = await app.dispatcher.guild.sub_user(guild_id, user_id)
|
||||||
|
for channel_id in channels:
|
||||||
|
for state in states:
|
||||||
|
await app.dispatcher.channel.sub(channel_id, state.session_id)
|
||||||
|
|
||||||
guild = await app.storage.get_guild_full(guild_id, user_id, 250)
|
guild = await app.storage.get_guild_full(guild_id, user_id, 250)
|
||||||
for state in states:
|
for state in states:
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,47 @@ IDENTIFY_SCHEMA = {
|
||||||
"shard": {"type": "list", "required": False},
|
"shard": {"type": "list", "required": False},
|
||||||
"presence": {"type": "dict", "required": False},
|
"presence": {"type": "dict", "required": False},
|
||||||
"intents": {"coerce": Intents, "required": False},
|
"intents": {"coerce": Intents, "required": False},
|
||||||
|
# TODO schema
|
||||||
|
"properties": {
|
||||||
|
"type": "dict",
|
||||||
|
"required": False,
|
||||||
|
"schema": {
|
||||||
|
"browser": {"type": "string", "required": False},
|
||||||
|
"client_build_number": {"type": "number", "required": False},
|
||||||
|
"client_event_source": {
|
||||||
|
"type": "string",
|
||||||
|
"required": False,
|
||||||
|
"nullable": True,
|
||||||
|
},
|
||||||
|
"client_version": {"type": "string", "required": False},
|
||||||
|
"distro": {"type": "string", "required": False},
|
||||||
|
"os": {"type": "string", "required": False},
|
||||||
|
"os_arch": {"type": "string", "required": False},
|
||||||
|
"os_version": {"type": "string", "required": False},
|
||||||
|
"release_channel": {"type": "string", "required": False},
|
||||||
|
"system_locale": {"type": "string", "required": False},
|
||||||
|
"window_manager": {"type": "string", "required": False},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"capabilities": {"type": "number", "required": False},
|
||||||
|
"client_state": {
|
||||||
|
"type": "dict",
|
||||||
|
"required": False,
|
||||||
|
"schema": {
|
||||||
|
# guild_hashes is a Dict with keys being guild ids and
|
||||||
|
# values being a list of 3 strings. this can not be
|
||||||
|
# validated by cerberus
|
||||||
|
"highest_last_message_id": {
|
||||||
|
"type": "string",
|
||||||
|
"required": False,
|
||||||
|
},
|
||||||
|
"read_state_version": {"type": "number", "required": False},
|
||||||
|
"user_guild_settings_version": {
|
||||||
|
"type": "number",
|
||||||
|
"required": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -90,11 +131,14 @@ REQ_GUILD_SCHEMA = {
|
||||||
"d": {
|
"d": {
|
||||||
"type": "dict",
|
"type": "dict",
|
||||||
"schema": {
|
"schema": {
|
||||||
"guild_id": {"type": "string", "required": True},
|
"user_ids": {
|
||||||
"user_ids": {"type": "list", "required": False},
|
"type": "list",
|
||||||
|
"required": False,
|
||||||
|
"schema": {"type": "string"},
|
||||||
|
},
|
||||||
"query": {"type": "string", "required": False},
|
"query": {"type": "string", "required": False},
|
||||||
"limit": {"type": "number", "required": False},
|
"limit": {"type": "number", "required": False},
|
||||||
"presences": {"type": "bool", "required": False},
|
"presences": {"type": "boolean", "required": False},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -506,20 +506,8 @@ class GatewayWebsocket:
|
||||||
channel_ids: List[int] = []
|
channel_ids: List[int] = []
|
||||||
|
|
||||||
for guild_id in guild_ids:
|
for guild_id in guild_ids:
|
||||||
await app.dispatcher.guild.sub(guild_id, session_id)
|
_, channels = await app.dispatcher.guild.sub_user(guild_id, user_id)
|
||||||
|
channel_ids.extend(channels)
|
||||||
# instead of calculating which channels to subscribe to
|
|
||||||
# inside guild dispatcher, we calculate them in here, so that
|
|
||||||
# we remove complexity of the dispatcher.
|
|
||||||
|
|
||||||
guild_chan_ids = await app.storage.get_channel_ids(guild_id)
|
|
||||||
for channel_id in guild_chan_ids:
|
|
||||||
perms = await get_permissions(
|
|
||||||
self.state.user_id, channel_id, storage=self.storage
|
|
||||||
)
|
|
||||||
|
|
||||||
if perms.bits.read_messages:
|
|
||||||
channel_ids.append(channel_id)
|
|
||||||
|
|
||||||
log.info("subscribing to {} guild channels", len(channel_ids))
|
log.info("subscribing to {} guild channels", len(channel_ids))
|
||||||
for channel_id in channel_ids:
|
for channel_id in channel_ids:
|
||||||
|
|
@ -664,7 +652,12 @@ class GatewayWebsocket:
|
||||||
|
|
||||||
async def handle_2(self, payload: Dict[str, Any]):
|
async def handle_2(self, payload: Dict[str, Any]):
|
||||||
"""Handle the OP 2 Identify packet."""
|
"""Handle the OP 2 Identify packet."""
|
||||||
payload = validate(payload, IDENTIFY_SCHEMA)
|
# do not validate given guild_hashes
|
||||||
|
payload_copy = dict(payload)
|
||||||
|
payload_copy["d"].get("client_state", {"guild_hashes": None}).pop(
|
||||||
|
"guild_hashes"
|
||||||
|
)
|
||||||
|
validate(payload_copy, IDENTIFY_SCHEMA)
|
||||||
data = payload["d"]
|
data = payload["d"]
|
||||||
token = data["token"]
|
token = data["token"]
|
||||||
|
|
||||||
|
|
@ -924,9 +917,18 @@ class GatewayWebsocket:
|
||||||
|
|
||||||
async def handle_8(self, payload: Dict):
|
async def handle_8(self, payload: Dict):
|
||||||
"""Handle OP 8 Request Guild Members."""
|
"""Handle OP 8 Request Guild Members."""
|
||||||
payload = validate(payload, REQ_GUILD_SCHEMA)
|
|
||||||
|
# we do not validate guild ids because it can either be a string
|
||||||
|
# or a list of strings and cerberus does not validate that.
|
||||||
|
payload_copy = dict(payload)
|
||||||
|
payload_copy["d"].pop("guild_id")
|
||||||
|
validate(payload_copy, REQ_GUILD_SCHEMA)
|
||||||
|
|
||||||
data = payload["d"]
|
data = payload["d"]
|
||||||
gids = data["guild_id"]
|
gids = data.get("guild_id")
|
||||||
|
# Discord actually sent this??
|
||||||
|
if gids is None:
|
||||||
|
return
|
||||||
|
|
||||||
uids, query, limit, presences = (
|
uids, query, limit, presences = (
|
||||||
data.get("user_ids", []),
|
data.get("user_ids", []),
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List
|
from typing import List, Tuple
|
||||||
|
|
||||||
from quart import current_app as app
|
from quart import current_app as app
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
@ -25,6 +25,8 @@ from logbook import Logger
|
||||||
from .dispatcher import DispatcherWithState, GatewayEvent
|
from .dispatcher import DispatcherWithState, GatewayEvent
|
||||||
from litecord.gateway.state import GatewayState
|
from litecord.gateway.state import GatewayState
|
||||||
from litecord.enums import EVENTS_TO_INTENTS
|
from litecord.enums import EVENTS_TO_INTENTS
|
||||||
|
from litecord.permissions import get_permissions
|
||||||
|
|
||||||
|
|
||||||
log = Logger(__name__)
|
log = Logger(__name__)
|
||||||
|
|
||||||
|
|
@ -33,7 +35,7 @@ def can_dispatch(event_type, event_data, state) -> bool:
|
||||||
# If we're sending to the same user for this kind of event,
|
# If we're sending to the same user for this kind of event,
|
||||||
# bypass event logic (always send)
|
# bypass event logic (always send)
|
||||||
if event_type == "GUILD_MEMBER_UPDATE":
|
if event_type == "GUILD_MEMBER_UPDATE":
|
||||||
user_id = int(event_data["user"])
|
user_id = int(event_data["user"]["id"])
|
||||||
return user_id == state.user_id
|
return user_id == state.user_id
|
||||||
|
|
||||||
# TODO Guild Create and Req Guild Members have specific
|
# TODO Guild Create and Req Guild Members have specific
|
||||||
|
|
@ -48,12 +50,26 @@ def can_dispatch(event_type, event_data, state) -> bool:
|
||||||
class GuildDispatcher(DispatcherWithState[int, str, GatewayEvent, List[str]]):
|
class GuildDispatcher(DispatcherWithState[int, str, GatewayEvent, List[str]]):
|
||||||
"""Guild backend for Pub/Sub."""
|
"""Guild backend for Pub/Sub."""
|
||||||
|
|
||||||
async def sub_user(self, guild_id: int, user_id: int) -> List[GatewayState]:
|
async def sub_user(
|
||||||
|
self, guild_id: int, user_id: int
|
||||||
|
) -> Tuple[List[GatewayState], List[int]]:
|
||||||
states = app.state_manager.fetch_states(user_id, guild_id)
|
states = app.state_manager.fetch_states(user_id, guild_id)
|
||||||
for state in states:
|
for state in states:
|
||||||
await self.sub(guild_id, state.session_id)
|
await self.sub(guild_id, state.session_id)
|
||||||
|
|
||||||
return states
|
# instead of calculating which channels to subscribe to
|
||||||
|
# inside guild dispatcher, we calculate them in here, so that
|
||||||
|
# we remove complexity of the dispatcher.
|
||||||
|
|
||||||
|
guild_chan_ids = await app.storage.get_channel_ids(guild_id)
|
||||||
|
channel_ids = []
|
||||||
|
for channel_id in guild_chan_ids:
|
||||||
|
perms = await get_permissions(user_id, channel_id)
|
||||||
|
|
||||||
|
if perms.bits.read_messages:
|
||||||
|
channel_ids.append(channel_id)
|
||||||
|
|
||||||
|
return states, channel_ids
|
||||||
|
|
||||||
async def dispatch_filter(
|
async def dispatch_filter(
|
||||||
self, guild_id: int, filter_function, event: GatewayEvent
|
self, guild_id: int, filter_function, event: GatewayEvent
|
||||||
|
|
|
||||||
|
|
@ -870,6 +870,22 @@ class GuildMemberList:
|
||||||
session_ids_new, new_user_index
|
session_ids_new, new_user_index
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _pres_update_remove(
|
||||||
|
self, user_id: int, old_group: GroupID, old_index: int
|
||||||
|
):
|
||||||
|
log.debug(
|
||||||
|
"removal update: uid={} old={} rel_idx={} new={}",
|
||||||
|
user_id,
|
||||||
|
old_group,
|
||||||
|
old_index,
|
||||||
|
)
|
||||||
|
|
||||||
|
old_user_index = self._get_item_index(user_id)
|
||||||
|
assert old_user_index is not None
|
||||||
|
self.list.data[old_group].remove(user_id)
|
||||||
|
session_ids_old = list(self._get_subs(old_user_index))
|
||||||
|
return await self._resync(session_ids_old, old_user_index)
|
||||||
|
|
||||||
async def new_member(self, user_id: int):
|
async def new_member(self, user_id: int):
|
||||||
"""Insert a new member."""
|
"""Insert a new member."""
|
||||||
if not self.list:
|
if not self.list:
|
||||||
|
|
@ -1019,14 +1035,6 @@ class GuildMemberList:
|
||||||
old_presence = self.list.presences[user_id]
|
old_presence = self.list.presences[user_id]
|
||||||
has_nick = "nick" in partial_presence
|
has_nick = "nick" in partial_presence
|
||||||
|
|
||||||
# partial presences don't have 'nick'. we only use it
|
|
||||||
# as a flag that we're doing a mixed update (complex
|
|
||||||
# but without any inter-group changes)
|
|
||||||
try:
|
|
||||||
partial_presence.pop("nick")
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for group, member_ids in self.list:
|
for group, member_ids in self.list:
|
||||||
try:
|
try:
|
||||||
old_index = member_ids.index(user_id)
|
old_index = member_ids.index(user_id)
|
||||||
|
|
@ -1051,7 +1059,6 @@ class GuildMemberList:
|
||||||
status = partial_presence.get("status", old_presence["status"])
|
status = partial_presence.get("status", old_presence["status"])
|
||||||
|
|
||||||
# calculate a possible new group
|
# calculate a possible new group
|
||||||
# TODO: handle when new_group is None (member loses perms)
|
|
||||||
new_group = await self._get_group_for_member(user_id, roles, status)
|
new_group = await self._get_group_for_member(user_id, roles, status)
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
|
|
@ -1071,8 +1078,16 @@ class GuildMemberList:
|
||||||
# nickname changes, treat this as a simple update
|
# nickname changes, treat this as a simple update
|
||||||
if old_group == new_group and not has_nick:
|
if old_group == new_group and not has_nick:
|
||||||
return await self._pres_update_simple(user_id)
|
return await self._pres_update_simple(user_id)
|
||||||
|
elif new_group is None:
|
||||||
return await self._pres_update_complex(user_id, old_group, old_index, new_group)
|
# The user is being removed from the overall list.
|
||||||
|
#
|
||||||
|
# This happens because they lost permissions to the relevant
|
||||||
|
# channel.
|
||||||
|
return await self._pres_update_remove(user_id, old_group, old_index)
|
||||||
|
else:
|
||||||
|
return await self._pres_update_complex(
|
||||||
|
user_id, old_group, old_index, new_group
|
||||||
|
)
|
||||||
|
|
||||||
async def new_role(self, role: dict):
|
async def new_role(self, role: dict):
|
||||||
"""Add a new role to the list.
|
"""Add a new role to the list.
|
||||||
|
|
|
||||||
|
|
@ -270,6 +270,12 @@ GUILD_CREATE = {
|
||||||
"explicit_content_filter": {"type": "explicit", "default": 0},
|
"explicit_content_filter": {"type": "explicit", "default": 0},
|
||||||
"roles": {"type": "list", "required": False, "schema": PARTIAL_ROLE_GUILD_CREATE},
|
"roles": {"type": "list", "required": False, "schema": PARTIAL_ROLE_GUILD_CREATE},
|
||||||
"channels": {"type": "list", "default": [], "schema": PARTIAL_CHANNEL_GUILD_CREATE},
|
"channels": {"type": "list", "default": [], "schema": PARTIAL_CHANNEL_GUILD_CREATE},
|
||||||
|
# not supported
|
||||||
|
"system_channel_id": {"coerce": int, "required": False, "nullable": True},
|
||||||
|
"guild_template_code": {
|
||||||
|
"type": "string",
|
||||||
|
"required": False,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -424,7 +430,7 @@ INVITE = {
|
||||||
"max_age": {
|
"max_age": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 86400,
|
"max": 666666, # TODO find correct max value
|
||||||
# a day
|
# a day
|
||||||
"default": 86400,
|
"default": 86400,
|
||||||
},
|
},
|
||||||
|
|
@ -445,6 +451,7 @@ INVITE = {
|
||||||
"nullable": True,
|
"nullable": True,
|
||||||
}, # discord client sends invite code there
|
}, # discord client sends invite code there
|
||||||
# sent by official client, unknown purpose
|
# sent by official client, unknown purpose
|
||||||
|
"target_type": {"type": "string", "required": False, "nullable": True},
|
||||||
"target_user_id": {"type": "snowflake", "required": False, "nullable": True},
|
"target_user_id": {"type": "snowflake", "required": False, "nullable": True},
|
||||||
"target_user_type": {"type": "number", "required": False, "nullable": True},
|
"target_user_type": {"type": "number", "required": False, "nullable": True},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue