diff --git a/litecord/dispatcher.py b/litecord/dispatcher.py index aa5b12d..6a042d6 100644 --- a/litecord/dispatcher.py +++ b/litecord/dispatcher.py @@ -4,7 +4,7 @@ from typing import Any from logbook import Logger from .pubsub import GuildDispatcher, MemberDispatcher, \ - UserDispatcher, ChannelDispatcher + UserDispatcher, ChannelDispatcher, FriendDispatcher log = Logger(__name__) @@ -20,8 +20,7 @@ class EventDispatcher: 'member': MemberDispatcher(self), 'channel': ChannelDispatcher(self), 'user': UserDispatcher(self), - - # TODO: channel, friends + 'friend': FriendDispatcher(self), } async def action(self, backend_str: str, action: str, key, identifier): diff --git a/litecord/gateway/websocket.py b/litecord/gateway/websocket.py index 3c8eb2f..bc45d80 100644 --- a/litecord/gateway/websocket.py +++ b/litecord/gateway/websocket.py @@ -285,8 +285,8 @@ class GatewayWebsocket: self.state.user_id ) - async def subscribe_guilds(self): - """Subscribe to all available guilds and DM channels. + async def subscribe_all(self): + """Subscribe to all guilds, DM channels, and friends. Subscribing to channels is already handled by GuildDispatcher.sub @@ -304,6 +304,13 @@ class GatewayWebsocket: log.info('subscribing to {} dms', len(dm_ids)) await self.ext.dispatcher.sub_many('channel', user_id, dm_ids) + # subscribe to all friends + # (their friends will also subscribe back + # when they come online) + friend_ids = await self.storage.get_friend_ids(user_id) + log.info('subscribing to {} friends', len(friend_ids)) + await self.ext.dispatcher.sub_many('friend', user_id, friend_ids) + async def update_status(self, status: dict): """Update the status of the current websocket connection.""" if status is None: @@ -398,7 +405,7 @@ class GatewayWebsocket: self.ext.state_manager.insert(self.state) await self.update_status(presence) - await self.subscribe_guilds() + await self.subscribe_all() await self.dispatch_ready() async def handle_3(self, payload: Dict[str, Any]): diff --git a/litecord/presence.py b/litecord/presence.py index 45a9bdd..3666f3e 100644 --- a/litecord/presence.py +++ b/litecord/presence.py @@ -96,9 +96,6 @@ class PresenceManager: """Dispatch a Presence update to an entire guild.""" state = dict(new_state) - if state['status'] == 'invisible': - state['status'] = 'offline' - member = await self.storage.get_member_data_one(guild_id, user_id) game = state['game'] @@ -117,14 +114,33 @@ class PresenceManager: } ) - async def dispatch_pres(self, user_id: int, state): - """Dispatch a new presence to all guilds the user is in.""" - # TODO: account for sharding + async def dispatch_pres(self, user_id: int, state: dict): + """Dispatch a new presence to all guilds the user is in. + + Also dispatches the presence to all the users' friends + """ + if state['status'] == 'invisible': + state['status'] = 'offline' + guild_ids = await self.storage.get_user_guilds(user_id) for guild_id in guild_ids: await self.dispatch_guild_pres(guild_id, user_id, state) + # dispatch to all friends that are subscribed to them + user = await self.storage.get_user(user_id) + game = state['game'] + + await self.dispatcher.dispatch( + 'friend', user_id, 'PRESENCE_UPDATE', { + 'user': user, + 'status': state['status'], + + # rich presence stuff + 'game': game, + 'activities': [game] if game else [] + }) + async def friend_presences(self, friend_ids: int) -> List[Dict[str, Any]]: """Fetch presences for a group of users. diff --git a/litecord/pubsub/__init__.py b/litecord/pubsub/__init__.py index 2021ca8..7320867 100644 --- a/litecord/pubsub/__init__.py +++ b/litecord/pubsub/__init__.py @@ -2,6 +2,8 @@ from .guild import GuildDispatcher from .member import MemberDispatcher from .user import UserDispatcher from .channel import ChannelDispatcher +from .friend import FriendDispatcher __all__ = ['GuildDispatcher', 'MemberDispatcher', - 'UserDispatcher', 'ChannelDispatcher'] + 'UserDispatcher', 'ChannelDispatcher', + 'FriendDispatcher'] diff --git a/litecord/pubsub/friend.py b/litecord/pubsub/friend.py new file mode 100644 index 0000000..eef5cfe --- /dev/null +++ b/litecord/pubsub/friend.py @@ -0,0 +1,22 @@ +from logbook import Logger + +from .dispatcher import DispatcherWithState + +log = Logger(__name__) + + +class FriendDispatcher(DispatcherWithState): + KEY_TYPE = int + VAL_TYPE = int + + async def dispatch(self, user_id: int, event, data): + """Dispatch an event to all of a users' friends.""" + peer_ids = self.state[user_id] + dispatched = 0 + + for peer_id in peer_ids: + dispatched += await self.main_dispatcher.dispatch( + 'user', peer_id, event, data) + + log.info('dispatched uid={} {!r} to {} states', + user_id, event, dispatched) diff --git a/litecord/storage.py b/litecord/storage.py index 59789ce..d1efc3c 100644 --- a/litecord/storage.py +++ b/litecord/storage.py @@ -771,6 +771,14 @@ class Storage: return res + async def get_friend_ids(self, user_id: int) -> List[int]: + """Get all friend IDs for a user.""" + rels = await self.get_relationships(user_id) + + return [int(r['user']['id']) + for r in rels + if r['type'] == RelationshipType.FRIEND.value] + async def get_dm(self, dm_id: int, user_id: int = None): dm_chan = await self.get_channel(dm_id)