Пример #1
0
    def update_read_timestamp(self, read_timestamp=None):
        """Update the timestamp of the latest event which has been read.

        By default, the timestamp of the newest event is used.

        This method will avoid making an API request if it will have no effect.

        Raises hangups.NetworkError if the timestamp can not be updated.
        """
        if read_timestamp is None:
            read_timestamp = self.events[-1].timestamp
        if read_timestamp > self.latest_read_timestamp:
            logger.info(
                "Setting {} latest_read_timestamp from {} to {}".format(
                    self.id_, self.latest_read_timestamp, read_timestamp
                )
            )
            # Prevent duplicate requests by updating the conversation now.
            state = self._conversation.self_conversation_state
            state.self_read_state.latest_read_timestamp = parsers.to_timestamp(read_timestamp)
            try:
                yield from self._client.update_watermark(
                    hangouts_pb2.UpdateWatermarkRequest(
                        request_header=self._client.get_request_header(),
                        conversation_id=hangouts_pb2.ConversationId(id=self.id_),
                        last_read_timestamp=parsers.to_timestamp(read_timestamp),
                    )
                )
            except exceptions.NetworkError as e:
                logger.warning("Failed to update read timestamp: {}".format(e))
                raise
Пример #2
0
    def update_conversation(self, conversation):
        """Update the internal state of the conversation.

        This method is used by :class:`ConversationList` to maintain this
        instance.

        Args:
            conversation: ``Conversation`` message.
        """
        # StateUpdate.conversation is actually a delta; fields that aren't
        # specified are assumed to be unchanged. Until this class is
        # refactored, hide this by saving and restoring previous values where
        # necessary.

        # delivery_medium_option
        new_state = conversation.self_conversation_state
        if not new_state.delivery_medium_option:
            old_state = self._conversation.self_conversation_state
            new_state.delivery_medium_option.extend(
                old_state.delivery_medium_option)

        # latest_read_timestamp
        old_timestamp = self.latest_read_timestamp
        self._conversation = conversation
        if parsers.to_timestamp(self.latest_read_timestamp) == 0:
            self_conversation_state = (
                self._conversation.self_conversation_state)
            self_conversation_state.self_read_state.latest_read_timestamp = (
                parsers.to_timestamp(old_timestamp))
Пример #3
0
    def update_conversation(self, conversation):
        """Update the internal Conversation."""
        # StateUpdate.conversation is actually a delta; fields that aren't
        # specified are assumed to be unchanged. Until this class is
        # refactored, hide this by saving and restoring previous values where
        # necessary.

        # delivery_medium_option
        new_state = conversation.self_conversation_state
        if len(new_state.delivery_medium_option) == 0:
            old_state = self._conversation.self_conversation_state
            new_state.delivery_medium_option.extend(
                old_state.delivery_medium_option
            )

        # latest_read_timestamp
        old_timestamp = self.latest_read_timestamp
        self._conversation = conversation
        if parsers.to_timestamp(self.latest_read_timestamp) == 0:
            self_conversation_state = (
                self._conversation.self_conversation_state
            )
            self_conversation_state.self_read_state.latest_read_timestamp = (
                parsers.to_timestamp(old_timestamp)
            )
Пример #4
0
    def update_read_timestamp(self, read_timestamp=None):
        """Update the timestamp of the latest event which has been read.

        This method will avoid making an API request if it will have no effect.

        Args:
            read_timestamp (datetime.datetime): (optional) Timestamp to set.
                Defaults to the timestamp of the newest event.

        Raises:
            .NetworkError: If the timestamp cannot be updated.
        """
        if read_timestamp is None:
            read_timestamp = (self.events[-1].timestamp if self.events else
                              datetime.datetime.now(datetime.timezone.utc))
        if read_timestamp > self.latest_read_timestamp:
            logger.info(
                'Setting {} latest_read_timestamp from {} to {}'.format(
                    self.id_, self.latest_read_timestamp, read_timestamp))
            # Prevent duplicate requests by updating the conversation now.
            state = self._conversation.self_conversation_state
            state.self_read_state.latest_read_timestamp = (
                parsers.to_timestamp(read_timestamp))
            try:
                yield from self._client.update_watermark(
                    hangouts_pb2.UpdateWatermarkRequest(
                        request_header=self._client.get_request_header(),
                        conversation_id=hangouts_pb2.ConversationId(
                            id=self.id_),
                        last_read_timestamp=parsers.to_timestamp(
                            read_timestamp),
                    ))
            except exceptions.NetworkError as e:
                logger.warning('Failed to update read timestamp: {}'.format(e))
                raise
    def update_conversation(self, conversation):
        """Update the internal Conversation."""
        # StateUpdate.conversation is actually a delta; fields that aren't
        # specified are assumed to be unchanged. Until this class is
        # refactored, hide this by saving and restoring previous values where
        # necessary.

        # delivery_medium_option
        new_state = conversation.self_conversation_state
        if len(new_state.delivery_medium_option) == 0:
            old_state = self._conversation.self_conversation_state
            new_state.delivery_medium_option.extend(
                old_state.delivery_medium_option
            )

        # latest_read_timestamp
        old_timestamp = self.latest_read_timestamp
        self._conversation = conversation
        if parsers.to_timestamp(self.latest_read_timestamp) == 0:
            self_conversation_state = (
                self._conversation.self_conversation_state
            )
            self_conversation_state.self_read_state.latest_read_timestamp = (
                parsers.to_timestamp(old_timestamp)
            )
Пример #6
0
 def update_conversation(self, client_conversation):
     """Update the internal ClientConversation."""
     # When latest_read_timestamp is 0, this seems to indicate no change
     # from the previous value. Word around this by saving and restoring the
     # previous value.
     old_timestamp = self.latest_read_timestamp
     self._conversation = client_conversation
     if parsers.to_timestamp(self.latest_read_timestamp) == 0:
         self_conversation_state = self._conversation.self_conversation_state
         self_conversation_state.self_read_state.latest_read_timestamp = (
             parsers.to_timestamp(old_timestamp))
Пример #7
0
 def update_conversation(self, client_conversation):
     """Update the internal ClientConversation."""
     # When latest_read_timestamp is 0, this seems to indicate no change
     # from the previous value. Word around this by saving and restoring the
     # previous value.
     old_timestamp = self.latest_read_timestamp
     self._conversation = client_conversation
     if parsers.to_timestamp(self.latest_read_timestamp) == 0:
         self_conversation_state = self._conversation.self_conversation_state
         self_conversation_state.self_read_state.latest_read_timestamp = (
             parsers.to_timestamp(old_timestamp)
         )
Пример #8
0
 def _sync(self):
     """Sync conversation state and events that could have been missed."""
     logger.info("Syncing events since {}".format(self._sync_timestamp))
     try:
         res = yield from self._client.sync_all_new_events(
             hangouts_pb2.SyncAllNewEventsRequest(
                 request_header=self._client.get_request_header(),
                 last_sync_timestamp=parsers.to_timestamp(self._sync_timestamp),
                 max_response_size_bytes=1048576,  # 1 MB
             )
         )
     except exceptions.NetworkError as e:
         logger.warning("Failed to sync events, some events may be lost: {}".format(e))
     else:
         for conv_state in res.conversation_state:
             conv_id = conv_state.conversation_id.id
             conv = self._conv_dict.get(conv_id, None)
             if conv is not None:
                 conv.update_conversation(conv_state.conversation)
                 for event_ in conv_state.event:
                     timestamp = parsers.from_timestamp(event_.timestamp)
                     if timestamp > self._sync_timestamp:
                         # This updates the sync_timestamp for us, as well
                         # as triggering events.
                         yield from self._on_event(event_)
             else:
                 self.add_conversation(conv_state.conversation, conv_state.event)
Пример #9
0
    def leave(self):
        """Leave conversation.

        Raises hangups.NetworkError if conversation cannot be left.
        """
        is_group_conversation = self._conversation.type == hangouts_pb2.CONVERSATION_TYPE_GROUP
        try:
            if is_group_conversation:
                yield from self._client.remove_user(
                    hangouts_pb2.RemoveUserRequest(
                        request_header=self._client.get_request_header(),
                        event_request_header=self._get_event_request_header(),
                    )
                )
            else:
                yield from self._client.delete_conversation(
                    hangouts_pb2.DeleteConversationRequest(
                        request_header=self._client.get_request_header(),
                        conversation_id=hangouts_pb2.ConversationId(id=self.id_),
                        delete_upper_bound_timestamp=parsers.to_timestamp(
                            datetime.datetime.now(tz=datetime.timezone.utc)
                        ),
                    )
                )
        except exceptions.NetworkError as e:
            logger.warning("Failed to leave conversation: {}".format(e))
            raise
Пример #10
0
    def leave(self):
        """Leave this conversation.

        Raises:
            .NetworkError: If conversation cannot be left.
        """
        is_group_conversation = (
            self._conversation.type == hangouts_pb2.CONVERSATION_TYPE_GROUP)
        try:
            if is_group_conversation:
                yield from self._client.remove_user(
                    hangouts_pb2.RemoveUserRequest(
                        request_header=self._client.get_request_header(),
                        event_request_header=self._get_event_request_header(),
                    ))
            else:
                yield from self._client.delete_conversation(
                    hangouts_pb2.DeleteConversationRequest(
                        request_header=self._client.get_request_header(),
                        conversation_id=hangouts_pb2.ConversationId(
                            id=self.id_),
                        delete_upper_bound_timestamp=parsers.to_timestamp(
                            datetime.datetime.now(tz=datetime.timezone.utc))))
        except exceptions.NetworkError as e:
            logger.warning('Failed to leave conversation: {}'.format(e))
            raise
Пример #11
0
    def syncallnewevents(self, timestamp):
        """List all events occurring at or after timestamp.

        This method requests protojson rather than json so we have one chat
        message parser rather than two.

        timestamp: datetime.datetime instance specifying the time after
        which to return all events occurring in.

        Raises hangups.NetworkError if the request fails.

        Returns a ClientSyncAllNewEventsResponse.
        """
        res = yield from self._request('conversations/syncallnewevents', [
            self._get_request_header(),
            # last_sync_timestamp
            parsers.to_timestamp(timestamp),
            [], None, [], False, [],
            1048576  # max_response_size_bytes
        ], use_json=False)
        try:
            res = schemas.CLIENT_SYNC_ALL_NEW_EVENTS_RESPONSE.parse(
                javascript.loads(res.body.decode())
            )
        except ValueError as e:
            raise exceptions.NetworkError('Response failed to parse: {}'
                                          .format(e))
        # can return 200 but still contain an error
        status = res.response_header.status
        if status != 1:
            raise exceptions.NetworkError('Response status is \'{}\''
                                          .format(status))
        return res
Пример #12
0
 def _sync(self):
     """Sync conversation state and events that could have been missed."""
     logger.info('Syncing events since {}'.format(self._sync_timestamp))
     try:
         res = yield from self._client.sync_all_new_events(
             hangouts_pb2.SyncAllNewEventsRequest(
                 request_header=self._client.get_request_header(),
                 last_sync_timestamp=parsers.to_timestamp(
                     self._sync_timestamp),
                 max_response_size_bytes=1048576,  # 1 MB
             ))
     except exceptions.NetworkError as e:
         logger.warning(
             'Failed to sync events, some events may be lost: {}'.format(e))
     else:
         for conv_state in res.conversation_state:
             conv_id = conv_state.conversation_id.id
             conv = self._conv_dict.get(conv_id, None)
             if conv is not None:
                 conv.update_conversation(conv_state.conversation)
                 for event_ in conv_state.event:
                     timestamp = parsers.from_timestamp(event_.timestamp)
                     if timestamp > self._sync_timestamp:
                         # This updates the sync_timestamp for us, as well
                         # as triggering events.
                         yield from self._on_event(event_)
             else:
                 self._add_conversation(conv_state.conversation,
                                        conv_state.event)
Пример #13
0
    def update_read_timestamp(self, read_timestamp=None):
        """Update the timestamp of the latest event which has been read.

        By default, the timestamp of the newest event is used.

        This method will avoid making an API request if it will have no effect.

        Raises hangups.NetworkError if the timestamp can not be updated.
        """
        if read_timestamp is None:
            read_timestamp = self.events[-1].timestamp
        if read_timestamp > self.latest_read_timestamp:
            logger.info(
                'Setting {} latest_read_timestamp from {} to {}'.format(
                    self.id_, self.latest_read_timestamp, read_timestamp))
            # Prevent duplicate requests by updating the conversation now.
            state = self._conversation.self_conversation_state
            state.self_read_state.latest_read_timestamp = (
                parsers.to_timestamp(read_timestamp))
            try:
                yield from self._client.updatewatermark(
                    self.id_, read_timestamp)
            except exceptions.NetworkError as e:
                logger.warning('Failed to update read timestamp: {}'.format(e))
                raise
Пример #14
0
    def syncallnewevents(self, timestamp):
        """List all events occuring at or after timestamp.

        This method requests protojson rather than json so we have one chat
        message parser rather than two.

        timestamp: datetime.datetime instance specifying the time after
        which to return all events occuring in.

        Raises hangups.NetworkError if the request fails.

        Returns a ClientSyncAllNewEventsResponse.
        """
        res = yield from self._request('conversations/syncallnewevents', [
            self._get_request_header(),
            # last_sync_timestamp
            parsers.to_timestamp(timestamp),
            [], None, [], False, [],
            1048576 # max_response_size_bytes
        ], use_json=False)
        try:
            res = schemas.CLIENT_SYNC_ALL_NEW_EVENTS_RESPONSE.parse(
                javascript.loads(res.body.decode())
            )
        except ValueError as e:
            raise exceptions.NetworkError('Response failed to parse: {}'
                                          .format(e))
        # can return 200 but still contain an error
        status = res.response_header.status
        if status != 1:
            raise exceptions.NetworkError('Response status is \'{}\''
                                          .format(status))
        return res
    def get_events(self, event_id=None, max_events=50):
        """Return list of ConversationEvents ordered newest-first.

        If event_id is specified, return events preceding this event.

        This method will make an API request to load historical events if
        necessary. If the beginning of the conversation is reached, an empty
        list will be returned.

        Raises KeyError if event_id does not correspond to a known event.

        Raises hangups.NetworkError if the events could not be requested.
        """
        if event_id is None:
            # If no event_id is provided, return the newest events in this
            # conversation.
            conv_events = self._events[-1 * max_events:]
        else:
            # If event_id is provided, return the events we have that are
            # older, or request older events if event_id corresponds to the
            # oldest event we have.
            conv_event = self.get_event(event_id)
            if self._events[0].id_ != event_id:
                conv_events = self._events[self._events.index(conv_event) + 1:]
            else:
                logger.info(
                    'Loading events for conversation {} before {}'.format(
                        self.id_, conv_event.timestamp))
                res = yield from self._client.get_conversation(
                    hangouts_pb2.GetConversationRequest(
                        request_header=self._client.get_request_header(),
                        conversation_spec=hangouts_pb2.ConversationSpec(
                            conversation_id=hangouts_pb2.ConversationId(
                                id=self.id_)),
                        include_event=True,
                        max_events_per_conversation=max_events,
                        event_continuation_token=(
                            hangouts_pb2.EventContinuationToken(
                                event_timestamp=parsers.to_timestamp(
                                    conv_event.timestamp)))))
                conv_events = [
                    self._wrap_event(event)
                    for event in res.conversation_state.event
                ]
                logger.info('Loaded {} events for conversation {}'.format(
                    len(conv_events), self.id_))
                # Iterate though the events newest to oldest.
                for conv_event in reversed(conv_events):
                    # Add event as the new oldest event, unless we already have
                    # it.
                    if conv_event.id_ not in self._events_dict:
                        self._events.insert(0, conv_event)
                        self._events_dict[conv_event.id_] = conv_event
                    else:
                        # If this happens, there's probably a bug.
                        logger.info(
                            'Conversation %s ignoring duplicate event %s',
                            self.id_, conv_event.id_)
        return conv_events
Пример #16
0
 async def leave_private_conversation(self, conversation_id):
     await self._client.delete_conversation(DeleteConversationRequest(
         request_header=self._client.get_request_header(),
         conversation_id=conversation_id,
         delete_upper_bound_timestamp=parsers.to_timestamp(
             datetime.now(tz=timezone.utc)
         )
     ))
Пример #17
0
 def _on_watermark_notification(self, notif):
     """Update the conversations latest_read_timestamp."""
     if self.get_user(notif.user_id).is_self:
         logger.info('latest_read_timestamp for {} updated to {}'.format(
             self.id_, notif.read_timestamp))
         self_conversation_state = self._conversation.self_conversation_state
         self_conversation_state.self_read_state.latest_read_timestamp = (
             parsers.to_timestamp(notif.read_timestamp))
Пример #18
0
 def _on_watermark_notification(self, notif):
     """Update the conversations latest_read_timestamp."""
     if self.get_user(notif.user_id).is_self:
         logger.info('latest_read_timestamp for {} updated to {}'
                     .format(self.id_, notif.read_timestamp))
         self_conversation_state = self._conversation.self_conversation_state
         self_conversation_state.self_read_state.latest_read_timestamp = (
             parsers.to_timestamp(notif.read_timestamp)
         )
Пример #19
0
    def get_events(self, event_id=None, max_events=50):
        """Return list of ConversationEvents ordered newest-first.

        If event_id is specified, return events preceding this event.

        This method will make an API request to load historical events if
        necessary. If the beginning of the conversation is reached, an empty
        list will be returned.

        Raises KeyError if event_id does not correspond to a known event.

        Raises hangups.NetworkError if the events could not be requested.
        """
        if event_id is None:
            # If no event_id is provided, return the newest events in this
            # conversation.
            conv_events = self._events[-1 * max_events :]
        else:
            # If event_id is provided, return the events we have that are
            # older, or request older events if event_id corresponds to the
            # oldest event we have.
            conv_event = self.get_event(event_id)
            if self._events[0].id_ != event_id:
                conv_events = self._events[self._events.index(conv_event) + 1 :]
            else:
                logger.info("Loading events for conversation {} before {}".format(self.id_, conv_event.timestamp))
                res = yield from self._client.get_conversation(
                    hangouts_pb2.GetConversationRequest(
                        request_header=self._client.get_request_header(),
                        conversation_spec=hangouts_pb2.ConversationSpec(
                            conversation_id=hangouts_pb2.ConversationId(id=self.id_)
                        ),
                        include_event=True,
                        max_events_per_conversation=max_events,
                        event_continuation_token=(
                            hangouts_pb2.EventContinuationToken(
                                event_timestamp=parsers.to_timestamp(conv_event.timestamp)
                            )
                        ),
                    )
                )
                conv_events = [self._wrap_event(event) for event in res.conversation_state.event]
                logger.info("Loaded {} events for conversation {}".format(len(conv_events), self.id_))
                # Iterate though the events newest to oldest.
                for conv_event in reversed(conv_events):
                    # Add event as the new oldest event, unless we already have
                    # it.
                    if conv_event.id_ not in self._events_dict:
                        self._events.insert(0, conv_event)
                        self._events_dict[conv_event.id_] = conv_event
                    else:
                        # If this happens, there's probably a bug.
                        logger.info("Conversation %s ignoring duplicate event %s", self.id_, conv_event.id_)
        return conv_events
Пример #20
0
    async def update_read_timestamp(self, read_timestamp=None):
        """Update the timestamp of the latest event which has been read.

        This method will avoid making an API request if it will have no effect.

        Args:
            read_timestamp (datetime.datetime): (optional) Timestamp to set.
                Defaults to the timestamp of the newest event.

        Raises:
            .NetworkError: If the timestamp cannot be updated.
        """
        if read_timestamp is None:
            read_timestamp = (self.events[-1].timestamp if self.events else
                              datetime.datetime.now(datetime.timezone.utc))
        if read_timestamp > self.latest_read_timestamp:
            logger.info(
                'Setting {} latest_read_timestamp from {} to {}'
                .format(self.id_, self.latest_read_timestamp, read_timestamp)
            )
            # Prevent duplicate requests by updating the conversation now.
            state = self._conversation.self_conversation_state
            state.self_read_state.latest_read_timestamp = (
                parsers.to_timestamp(read_timestamp)
            )
            try:
                await self._client.update_watermark(
                    hangouts_pb2.UpdateWatermarkRequest(
                        request_header=self._client.get_request_header(),
                        conversation_id=hangouts_pb2.ConversationId(
                            id=self.id_
                        ),
                        last_read_timestamp=parsers.to_timestamp(
                            read_timestamp
                        ),
                    )
                )
            except exceptions.NetworkError as e:
                logger.warning('Failed to update read timestamp: {}'.format(e))
                raise
Пример #21
0
    def updatewatermark(self, conv_id, read_timestamp):
        """Update the watermark (read timestamp) for a conversation.

        Raises hangups.NetworkError if the request fails.
        """
        request = hangouts_pb2.UpdateWatermarkRequest(
            request_header=self._get_request_header_pb(),
            conversation_id=hangouts_pb2.ConversationId(id=conv_id),
            last_read_timestamp=parsers.to_timestamp(read_timestamp),
        )
        response = hangouts_pb2.UpdateWatermarkResponse()
        yield from self._pb_request('conversations/updatewatermark', request,
                                    response)
        return response
Пример #22
0
    def updatewatermark(self, conv_id, read_timestamp):
        """Update the watermark (read timestamp) for a conversation.

        Raises hangups.NetworkError if the request fails.
        """
        res = yield from self._request('conversations/updatewatermark', [
            self._get_request_header(),
            # conversation_id
            [conv_id],
            # latest_read_timestamp
            parsers.to_timestamp(read_timestamp),
        ])
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            raise exceptions.NetworkError('Unexpected status: {}'
                                          .format(res_status))
Пример #23
0
    def updatewatermark(self, conv_id, read_timestamp):
        """Update the watermark (read timestamp) for a conversation.

        Raises hangups.NetworkError if the request fails.
        """
        res = yield from self._request('conversations/updatewatermark', [
            self._get_request_header(),
            # conversation_id
            [conv_id],
            # latest_read_timestamp
            parsers.to_timestamp(read_timestamp),
        ])
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            raise exceptions.NetworkError('Unexpected status: {}'
                                          .format(res_status))
Пример #24
0
 def _on_watermark_notification(self, notif):
     """Handle a watermark notification."""
     # Update the conversation:
     if self.get_user(notif.user_id).is_self:
         logger.info('latest_read_timestamp for {} updated to {}'.format(
             self.id_, notif.read_timestamp))
         self_conversation_state = (
             self._conversation.self_conversation_state)
         self_conversation_state.self_read_state.latest_read_timestamp = (
             parsers.to_timestamp(notif.read_timestamp))
     # Update the participants' watermarks:
     previous_timestamp = self._watermarks.get(notif.user_id,
                                               datetime.datetime.min)
     if notif.read_timestamp > previous_timestamp:
         logger.info(
             ('latest_read_timestamp for conv {} participant {}' +
              ' updated to {}').format(self.id_, notif.user_id.chat_id,
                                       notif.read_timestamp))
         self._watermarks[notif.user_id] = notif.read_timestamp
Пример #25
0
    def syncallnewevents(self, timestamp):
        """List all events occurring at or after timestamp.

        This method requests protojson rather than json so we have one chat
        message parser rather than two.

        timestamp: datetime.datetime instance specifying the time after
        which to return all events occurring in.

        Raises hangups.NetworkError if the request fails.

        Returns SyncAllNewEventsResponse.
        """
        request = hangouts_pb2.SyncAllNewEventsRequest(
            request_header=self._get_request_header_pb(),
            last_sync_timestamp=parsers.to_timestamp(timestamp),
            max_response_size_bytes=1048576,
        )
        response = hangouts_pb2.SyncAllNewEventsResponse()
        yield from self._pb_request('conversations/syncallnewevents', request,
                                    response)
        return response
Пример #26
0
    def deleteconversation(self, conversation_id):
        """Delete one-to-one conversation.

        conversation_id must be a valid conversation ID.

        Raises hangups.NetworkError if the request fails.
        """
        res = yield from self._request('conversations/deleteconversation', [
            self._get_request_header(),
            [conversation_id],
            # Not sure what timestamp should be there, last time I have tried
            # it Hangouts client in GMail sent something like now() - 5 hours
            parsers.to_timestamp(
                datetime.datetime.now(tz=datetime.timezone.utc)
            ),
            None, [],
        ])
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            raise exceptions.NetworkError('Unexpected status: {}'
                                          .format(res_status))
Пример #27
0
    def deleteconversation(self, conversation_id):
        """Delete one-to-one conversation.

        conversation_id must be a valid conversation ID.

        Raises hangups.NetworkError if the request fails.
        """
        res = yield from self._request('conversations/deleteconversation', [
            self._get_request_header(),
            [conversation_id],
            # Not sure what timestamp should be there, last time I have tried it
            # Hangouts client in GMail sent something like now() - 5 hours
            parsers.to_timestamp(
                datetime.datetime.now(tz=datetime.timezone.utc)
            ),
            None, [],
        ])
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            raise exceptions.NetworkError('Unexpected status: {}'
                                          .format(res_status))
Пример #28
0
    def getconversation(self, conversation_id, event_timestamp, max_events=50):
        """Return conversation events.

        This is mainly used for retrieving conversation scrollback. Events
        occurring before event_timestamp are returned, in order from oldest to
        newest.

        Raises hangups.NetworkError if the request fails.
        """
        res = yield from self._request(
            'conversations/getconversation',
            [
                self._get_request_header(),
                [[conversation_id], [], []],  # conversationSpec
                False,  # includeConversationMetadata
                True,  # includeEvents
                None,  # ???
                max_events,  # maxEventsPerConversation
                # eventContinuationToken (specifying timestamp is sufficient)
                [
                    None,  # eventId
                    None,  # storageContinuationToken
                    parsers.to_timestamp(event_timestamp),  # eventTimestamp
                ]
            ],
            use_json=False)
        try:
            res = schemas.CLIENT_GET_CONVERSATION_RESPONSE.parse(
                javascript.loads(res.body.decode()))
        except ValueError as e:
            raise exceptions.NetworkError(
                'Response failed to parse: {}'.format(e))
        # can return 200 but still contain an error
        status = res.response_header.status
        if status != 1:
            raise exceptions.NetworkError(
                'Response status is \'{}\''.format(status))
        return res
Пример #29
0
    def deleteconversation(self, conversation_id):
        """Delete one-to-one conversation.

        One-to-one conversations are "sticky"; they can't actually be deleted.
        This API clears the event history of the specified conversation up to
        delete_upper_bound_timestamp, hiding it if no events remain.

        conversation_id must be a valid conversation ID.

        Raises hangups.NetworkError if the request fails.
        """
        timestamp = parsers.to_timestamp(
            datetime.datetime.now(tz=datetime.timezone.utc)
        )
        request = hangouts_pb2.DeleteConversationRequest(
            request_header=self._get_request_header_pb(),
            conversation_id=hangouts_pb2.ConversationId(id=conversation_id),
            delete_upper_bound_timestamp=timestamp
        )
        response = hangouts_pb2.DeleteConversationResponse()
        yield from self._pb_request('conversations/deleteconversation',
                                    request, response)
        return response
Пример #30
0
 def _on_watermark_notification(self, notif):
     """Handle a watermark notification."""
     # Update the conversation:
     if self.get_user(notif.user_id).is_self:
         logger.info('latest_read_timestamp for {} updated to {}'
                     .format(self.id_, notif.read_timestamp))
         self_conversation_state = (
             self._conversation.self_conversation_state
         )
         self_conversation_state.self_read_state.latest_read_timestamp = (
             parsers.to_timestamp(notif.read_timestamp)
         )
     # Update the participants' watermarks:
     previous_timestamp = self._watermarks.get(
         notif.user_id,
         datetime.datetime.min.replace(tzinfo=datetime.timezone.utc)
     )
     if notif.read_timestamp > previous_timestamp:
         logger.info(('latest_read_timestamp for conv {} participant {}' +
                      ' updated to {}').format(self.id_,
                                               notif.user_id.chat_id,
                                               notif.read_timestamp))
         self._watermarks[notif.user_id] = notif.read_timestamp
Пример #31
0
    def getconversation(self, conversation_id, event_timestamp, max_events=50):
        """Return conversation events.

        This is mainly used for retrieving conversation scrollback. Events
        occurring before event_timestamp are returned, in order from oldest to
        newest.

        Raises hangups.NetworkError if the request fails.
        """
        request = hangouts_pb2.GetConversationRequest(
            request_header=self._get_request_header_pb(),
            conversation_spec=hangouts_pb2.ConversationSpec(
                conversation_id=hangouts_pb2.ConversationId(id=conversation_id)
            ),
            include_event=True,
            max_events_per_conversation=max_events,
            event_continuation_token=hangouts_pb2.EventContinuationToken(
                event_timestamp=parsers.to_timestamp(event_timestamp)
            ),
        )
        response = hangouts_pb2.GetConversationResponse()
        yield from self._pb_request('conversations/getconversation', request,
                                    response)
        return response
Пример #32
0
    def getconversation(self, conversation_id, event_timestamp, max_events=50):
        """Return conversation events.

        This is mainly used for retrieving conversation scrollback. Events
        occurring before event_timestamp are returned, in order from oldest to
        newest.

        Raises hangups.NetworkError if the request fails.
        """
        res = yield from self._request('conversations/getconversation', [
            self._get_request_header(),
            [[conversation_id], [], []],  # conversationSpec
            False,  # includeConversationMetadata
            True,  # includeEvents
            None,  # ???
            max_events,  # maxEventsPerConversation
            # eventContinuationToken (specifying timestamp is sufficient)
            [
                None,  # eventId
                None,  # storageContinuationToken
                parsers.to_timestamp(event_timestamp),  # eventTimestamp
            ]
        ], use_json=False)
        try:
            res = schemas.CLIENT_GET_CONVERSATION_RESPONSE.parse(
                javascript.loads(res.body.decode())
            )
        except ValueError as e:
            raise exceptions.NetworkError('Response failed to parse: {}'
                                          .format(e))
        # can return 200 but still contain an error
        status = res.response_header.status
        if status != 1:
            raise exceptions.NetworkError('Response status is \'{}\''
                                          .format(status))
        return res
Пример #33
0
    def get_events(self, event_id=None, max_events=50):
        """Get events from this conversation.

        Makes a request to load historical events if necessary.

        Args:
            event_id (str): (optional) If provided, return events preceding
                this event, otherwise return the newest events.
            max_events (int): Maximum number of events to return. Defaults to
                50.

        Returns:
            List of :class:`.ConversationEvent` instances, ordered
            newest-first.

        Raises:
            KeyError: If ``event_id`` does not correspond to a known event.
            .NetworkError: If the events could not be requested.
        """
        if event_id is None:
            # If no event_id is provided, return the newest events in this
            # conversation.
            conv_events = self._events[-1 * max_events:]
        else:
            # If event_id is provided, return the events we have that are
            # older, or request older events if event_id corresponds to the
            # oldest event we have.
            conv_event = self.get_event(event_id)
            if self._events[0].id_ != event_id:
                conv_events = self._events[self._events.index(conv_event) + 1:]
            else:
                logger.info(
                    'Loading events for conversation {} before {}'.format(
                        self.id_, conv_event.timestamp))
                res = yield from self._client.get_conversation(
                    hangouts_pb2.GetConversationRequest(
                        request_header=self._client.get_request_header(),
                        conversation_spec=hangouts_pb2.ConversationSpec(
                            conversation_id=hangouts_pb2.ConversationId(
                                id=self.id_)),
                        include_event=True,
                        max_events_per_conversation=max_events,
                        event_continuation_token=(
                            hangouts_pb2.EventContinuationToken(
                                event_timestamp=parsers.to_timestamp(
                                    conv_event.timestamp)))))
                conv_events = [
                    self._wrap_event(event)
                    for event in res.conversation_state.event
                ]
                logger.info('Loaded {} events for conversation {}'.format(
                    len(conv_events), self.id_))
                # Iterate though the events newest to oldest.
                for conv_event in reversed(conv_events):
                    # Add event as the new oldest event, unless we already have
                    # it.
                    if conv_event.id_ not in self._events_dict:
                        self._events.insert(0, conv_event)
                        self._events_dict[conv_event.id_] = conv_event
                    else:
                        # If this happens, there's probably a bug.
                        logger.info(
                            'Conversation %s ignoring duplicate event %s',
                            self.id_, conv_event.id_)
        return conv_events