Beispiel #1
0
 async def sync_chats(self, chats: ConversationList) -> None:
     self.chats = chats
     portals = {
         conv.id_: po.Portal.get_by_conversation(conv, self.gid)
         for conv in chats.get_all()
     }
     await self._sync_community_rooms(portals)
     self.chats.on_event.add_observer(
         self._ensure_future_proxy(self.on_event))
     self.chats.on_typing.add_observer(
         self._ensure_future_proxy(self.on_typing))
     self.log.debug("Fetching recent conversations to create portals for")
     res = await self.client.sync_recent_conversations(
         hangouts.SyncRecentConversationsRequest(
             request_header=self.client.get_request_header(),
             max_conversations=config["bridge.initial_chat_sync"],
             max_events_per_conversation=1,
             sync_filter=[hangouts.SYNC_FILTER_INBOX],
         ))
     res = sorted(
         (conv_state.conversation for conv_state in res.conversation_state),
         reverse=True,
         key=lambda conv: conv.self_conversation_state.sort_timestamp)
     res = (chats.get(conv.conversation_id.id) for conv in res)
     await asyncio.gather(*[
         po.Portal.get_by_conversation(info, self.gid).create_matrix_room(
             self, info) for info in res
     ],
                          loop=self.loop)
Beispiel #2
0
 async def sync_chats(self, chats: ConversationList) -> None:
     self.chats = chats
     self.chats_future.set_result(None)
     portals = {conv.id_: po.Portal.get_by_conversation(conv, self.gid)
                for conv in chats.get_all()}
     await self._sync_community_rooms(portals)
     self.chats.on_watermark_notification.add_observer(
         self._ensure_future_proxy(self.on_receipt))
     self.chats.on_event.add_observer(self._ensure_future_proxy(self.on_event))
     self.chats.on_typing.add_observer(self._ensure_future_proxy(self.on_typing))
     self.log.debug("Fetching recent conversations to create portals for")
     res = await self.client.sync_recent_conversations(hangouts.SyncRecentConversationsRequest(
         request_header=self.client.get_request_header(),
         max_conversations=config["bridge.initial_chat_sync"],
         max_events_per_conversation=1,
         sync_filter=[hangouts.SYNC_FILTER_INBOX],
     ))
     self.log.debug("Server returned %d conversations", len(res.conversation_state))
     convs = sorted(res.conversation_state, reverse=True,
                    key=lambda state: state.conversation.self_conversation_state.sort_timestamp)
     for state in convs:
         self.log.debug("Syncing %s", state.conversation_id.id)
         chat = chats.get(state.conversation_id.id)
         portal = po.Portal.get_by_conversation(chat, self.gid)
         if portal.mxid:
             await portal.update_matrix_room(self, chat)
             if len(state.event) > 0 and not DBMessage.get_by_gid(state.event[0].event_id):
                 self.log.debug("Last message %s in chat %s not found in db, backfilling...",
                                state.event[0].event_id, state.conversation_id.id)
                 await portal.backfill(self, is_initial=False)
         else:
             await portal.create_matrix_room(self, chat)
     await self.update_direct_chats()
Beispiel #3
0
async def _sync_all_conversations(client):
    """Sync all conversations by making paginated requests.

    Conversations are ordered by ascending sort timestamp.

    Args:
        client (Client): Connected client.

    Raises:
        NetworkError: If the requests fail.

    Returns:
        tuple of list of ``ConversationState`` messages and sync timestamp
    """
    conv_states = []
    sync_timestamp = None
    request = hangouts_pb2.SyncRecentConversationsRequest(
        request_header=client.get_request_header(),
        max_conversations=CONVERSATIONS_PER_REQUEST,
        max_events_per_conversation=1,
        sync_filter=[
            hangouts_pb2.SYNC_FILTER_INBOX,
            hangouts_pb2.SYNC_FILTER_ARCHIVED,
        ]
    )
    for _ in range(MAX_CONVERSATION_PAGES):
        logger.info(
            'Requesting conversations page %s', request.last_event_timestamp
        )
        response = await client.sync_recent_conversations(request)
        conv_states = list(response.conversation_state) + conv_states
        sync_timestamp = parsers.from_timestamp(
            # SyncRecentConversations seems to return a sync_timestamp 4
            # minutes before the present. To prevent SyncAllNewEvents later
            # breaking requesting events older than what we already have, use
            # current_server_time instead.
            response.response_header.current_server_time
        )
        if response.continuation_end_timestamp == 0:
            logger.info('Reached final conversations page')
            break
        else:
            request.last_event_timestamp = response.continuation_end_timestamp
    else:
        logger.warning('Exceeded maximum number of conversation pages')
    logger.info('Synced %s total conversations', len(conv_states))
    return conv_states, sync_timestamp
Beispiel #4
0
    def syncrecentconversations(self, max_conversations=100,
                                max_events_per_conversation=1):
        """List the contents of recent conversations, including messages.

        Similar to syncallnewevents, but returns a limited number of
        conversations rather than all conversations in a given date range.

        Can be used to retrieve archived conversations.

        Raises hangups.NetworkError if the request fails.
        """
        request = hangouts_pb2.SyncRecentConversationsRequest(
            request_header=self._get_request_header_pb(),
            max_conversations=max_conversations,
            max_events_per_conversation=max_events_per_conversation,
            sync_filter=[hangouts_pb2.SYNC_FILTER_INBOX],
        )
        response = hangouts_pb2.SyncRecentConversationsResponse()
        yield from self._pb_request('conversations/syncrecentconversations',
                                    request, response)
        return response
def build_user_conversation_list(client):
    """Return UserList from initial contact data and an additional request.

    The initial data contains the user's contacts, but there may be conversions
    containing users that are not in the contacts. This function takes care of
    requesting data for those users and constructing the UserList.
    """

    # Retrieve recent conversations so we can preemptively look up their
    # participants.
    sync_recent_conversations_response = (
        yield from client.sync_recent_conversations(
            hangouts_pb2.SyncRecentConversationsRequest(
                request_header=client.get_request_header(),
                max_conversations=100,
                max_events_per_conversation=1,
                sync_filter=[hangouts_pb2.SYNC_FILTER_INBOX],
            )))
    conv_states = sync_recent_conversations_response.conversation_state
    sync_timestamp = parsers.from_timestamp(
        # syncrecentconversations seems to return a sync_timestamp 4 minutes
        # before the present. To prevent syncallnewevents later breaking
        # requesting events older than what we already have, use
        # current_server_time instead.
        sync_recent_conversations_response.response_header.current_server_time)

    # Retrieve entities participating in all conversations.
    required_user_ids = set()
    for conv_state in conv_states:
        required_user_ids |= {
            user.UserID(chat_id=part.id.chat_id, gaia_id=part.id.gaia_id)
            for part in conv_state.conversation.participant_data
        }
    required_entities = []
    if required_user_ids:
        logger.debug(
            'Need to request additional users: {}'.format(required_user_ids))
        try:
            response = yield from client.get_entity_by_id(
                hangouts_pb2.GetEntityByIdRequest(
                    request_header=client.get_request_header(),
                    batch_lookup_spec=[
                        hangouts_pb2.EntityLookupSpec(
                            gaia_id=user_id.gaia_id,
                            create_offnetwork_gaia=True,
                        ) for user_id in required_user_ids
                    ],
                ))
            for entity_result in response.entity_result:
                required_entities.extend(entity_result.entity)
        except exceptions.NetworkError as e:
            logger.warning('Failed to request missing users: {}'.format(e))

    # Build list of conversation participants.
    conv_part_list = []
    for conv_state in conv_states:
        conv_part_list.extend(conv_state.conversation.participant_data)

    # Retrieve self entity.
    get_self_info_response = yield from client.get_self_info(
        hangouts_pb2.GetSelfInfoRequest(
            request_header=client.get_request_header(), ))
    self_entity = get_self_info_response.self_entity

    user_list = user.UserList(client, self_entity, required_entities,
                              conv_part_list)
    conversation_list = ConversationList(client, conv_states, user_list,
                                         sync_timestamp)
    return (user_list, conversation_list)
Beispiel #6
0
def build_user_conversation_list(client):
    """Build :class:`~UserList` and :class:`~ConversationList`.

    This method requests data necessary to build the list of conversations and
    users. Users that are not in the contact list but are participating in a
    conversation will also be retrieved.

    Args:
        client (Client): Connected client.

    Returns:
        (:class:`~UserList`, :class:`~ConversationList`):
            Tuple of built objects.
    """

    # Retrieve conversations in groups of CONVERSATIONS_PER_REQUEST.
    conv_states = []
    sync_timestamp, next_timestamp = None, None
    last_synced = CONVERSATIONS_PER_REQUEST

    while last_synced == CONVERSATIONS_PER_REQUEST:
        response = (
            yield from client.sync_recent_conversations(
                hangouts_pb2.SyncRecentConversationsRequest(
                    request_header=client.get_request_header(),
                    last_event_timestamp=next_timestamp,
                    max_conversations=CONVERSATIONS_PER_REQUEST,
                    max_events_per_conversation=1,
                    sync_filter=[
                        hangouts_pb2.SYNC_FILTER_INBOX,
                        hangouts_pb2.SYNC_FILTER_ARCHIVED,
                    ]
                )
            )
        )

        # Add these conversations to the list of states
        response_conv_states = response.conversation_state
        min_timestamp = float('inf')
        for conv_state in response_conv_states:
            conv_event = conv_state.event[0]
            if conv_event.timestamp < min_timestamp:
                min_timestamp = conv_event.timestamp
            conv_states.append(conv_state)

        # Update the number of conversations synced and sync timestamp
        last_synced = len(response_conv_states)
        sync_timestamp = parsers.from_timestamp(
            # SyncRecentConversations seems to return a sync_timestamp 4
            # minutes before the present. To prevent SyncAllNewEvents later
            # breaking requesting events older than what we already have, use
            # current_server_time instead.
            response.response_header.current_server_time
        )

        logger.debug('Added {} conversations'.format(last_synced))

        if math.isfinite(min_timestamp):
            # Start syncing conversations just before this one
            next_timestamp = min_timestamp - 1
        else:
            # No minimum timestamp; abort.
            next_timestamp = 0
            break

    logger.info('Synced {} total conversations'.format(len(conv_states)))

    # Retrieve entities participating in all conversations.
    required_user_ids = set()
    for conv_state in conv_states:
        required_user_ids |= {
            user.UserID(chat_id=part.id.chat_id, gaia_id=part.id.gaia_id)
            for part in conv_state.conversation.participant_data
        }
    required_entities = []
    if required_user_ids:
        logger.debug('Need to request additional users: {}'
                     .format(required_user_ids))
        try:
            response = yield from client.get_entity_by_id(
                hangouts_pb2.GetEntityByIdRequest(
                    request_header=client.get_request_header(),
                    batch_lookup_spec=[
                        hangouts_pb2.EntityLookupSpec(
                            gaia_id=user_id.gaia_id,
                            create_offnetwork_gaia=True,
                        )
                        for user_id in required_user_ids
                    ],
                )
            )
            for entity_result in response.entity_result:
                required_entities.extend(entity_result.entity)
        except exceptions.NetworkError as e:
            logger.warning('Failed to request missing users: {}'.format(e))

    # Build list of conversation participants.
    conv_part_list = []
    for conv_state in conv_states:
        conv_part_list.extend(conv_state.conversation.participant_data)

    # Retrieve self entity.
    get_self_info_response = yield from client.get_self_info(
        hangouts_pb2.GetSelfInfoRequest(
            request_header=client.get_request_header(),
        )
    )
    self_entity = get_self_info_response.self_entity

    user_list = user.UserList(client, self_entity, required_entities,
                              conv_part_list)
    conversation_list = ConversationList(client, conv_states,
                                         user_list, sync_timestamp)
    return (user_list, conversation_list)