Esempio n. 1
0
    def setchatname(self, conversation_id, name):
        """Set the name of a conversation.

        Raises hangups.NetworkError if the request fails.
        """
        client_generated_id = random.randint(0, 2**32)
        body = [
            self._get_request_header(), None, name, None,
            [[conversation_id], client_generated_id, 1]
        ]
        res = yield from self._request('conversations/renameconversation',
                                       body)
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            logger.warning(
                'renameconversation returned status {}'.format(res_status))
            raise exceptions.NetworkError()
Esempio n. 2
0
    def settyping(self, conversation_id, typing=schemas.TypingStatus.TYPING):
        """Send typing notification.

        conversation_id must be a valid conversation ID.
        typing must be a hangups.TypingStatus Enum.

        Raises hangups.NetworkError if the request fails.
        """
        res = yield from self._request('conversations/settyping', [
            self._get_request_header(),
            [conversation_id],
            typing.value
        ])
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            raise exceptions.NetworkError('Unexpected status: {}'
                                          .format(res_status))
Esempio n. 3
0
    def setconversationnotificationlevel(self, conversation_id, level):
        """Set the notification level of a conversation.

        Pass schemas.ClientNotificationLevel.QUIET to disable notifications,
        or schemas.ClientNotificationLevel.RING to enable them.

        Raises hangups.NetworkError if the request fails.
        """
        body = [self._get_request_header(), [conversation_id], level.value]
        res = yield from self._request(
            'conversations/setconversationnotificationlevel', body)
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            logger.warning(
                'setconversationnotificationlevel returned status {}'.format(
                    res_status))
            raise exceptions.NetworkError()
Esempio n. 4
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))
Esempio n. 5
0
    def sendeasteregg(self, conversation_id, easteregg):
        """Send a easteregg to a conversation.

        easteregg may not be empty.

        Raises hangups.NetworkError if the request fails.
        """
        body = [
            self._get_request_header(),
            [conversation_id],
            [easteregg, None, 1]
        ]
        res = yield from self._request('conversations/easteregg', body)
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            logger.warning('easteregg returned status {}'
                           .format(res_status))
            raise exceptions.NetworkError()
Esempio n. 6
0
    def setactiveclient(self, is_active, timeout_secs):
        """Set the active client.

        Raises hangups.NetworkError if the request fails.
        """
        res = yield from self._request('clients/setactiveclient', [
            self._get_request_header(),
            # is_active: whether the client is active or not
            is_active,
            # full_jid: user@domain/resource
            "{}/{}".format(self._email, self._client_id),
            # timeout_secs: timeout in seconds for this client to be active
            timeout_secs
        ])
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            raise exceptions.NetworkError('Unexpected status: {}'
                                          .format(res_status))
Esempio n. 7
0
    def sendchatmessage(
            self, conversation_id, segments, image_id=None,
            otr_status=schemas.OffTheRecordStatus.ON_THE_RECORD
    ):
        """Send a chat message to a conversation.

        conversation_id must be a valid conversation ID. segments must be a
        list of message segments to send, in pblite format.

        otr_status determines whether the message will be saved in the server's
        chat history. Note that the OTR status of the conversation is
        irrelevant, clients may send messages with whatever OTR status they
        like.

        image_id is an option ID of an image retrieved from
        Client.upload_image. If provided, the image will be attached to the
        message.

        Raises hangups.NetworkError if the request fails.
        """
        client_generated_id = random.randint(0, 2**32)
        body = [
            self._get_request_header(),
            None, None, None, [],
            [
                segments, []
            ],
            [[image_id, False]] if image_id else None,
            [
                [conversation_id],
                client_generated_id,
                otr_status.value,
            ],
            None, None, None, []
        ]
        res = yield from self._request('conversations/sendchatmessage', body)
        # sendchatmessage can return 200 but still contain an error
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            raise exceptions.NetworkError('Unexpected status: {}'
                                          .format(res_status))
Esempio n. 8
0
    def removeuser(self, conversation_id):
        """Leave group conversation.

        conversation_id must be a valid conversation ID.

        Raises hangups.NetworkError if the request fails.
        """
        client_generated_id = random.randint(0, 2**32)
        res = yield from self._request('conversations/removeuser', [
            self._get_request_header(),
            None,
            None,
            None,
            [[conversation_id], client_generated_id, 2],
        ])
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            raise exceptions.NetworkError(
                'Unexpected status: {}'.format(res_status))
Esempio n. 9
0
    def sendchatmessage(self, conversation_id, segments):
        """Send a chat message to a conversation.

        conversation_id must be a valid conversation ID. segments must be a
        list of message segments to send, in pblite format.

        Raises hangups.NetworkError if the request fails.
        """
        client_generated_id = random.randint(0, 2**32)
        body = [
            self._get_request_header(), None, None, None, [], [segments,
                                                               []], None,
            [[conversation_id], client_generated_id, 2], None, None, None, []
        ]
        res = yield from self._request('conversations/sendchatmessage', body)
        # sendchatmessage can return 200 but still contain an error
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            raise exceptions.NetworkError(
                'Unexpected status: {}'.format(res_status))
Esempio n. 10
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))
Esempio n. 11
0
    def _get_upload_session_status(res):
        """Parse the image upload response to obtain status.

        Args:
            res: http_utils.FetchResponse instance, the upload response

        Returns:
            dict, sessionStatus of the response

        Raises:
            hangups.NetworkError: If the upload request failed.
        """
        response = json.loads(res.body.decode())
        if 'sessionStatus' not in response:
            try:
                info = (response['errorMessage']['additionalInfo']
                        ['uploader_service.GoogleRupioAdditionalInfo']
                        ['completionInfo']['customerSpecificInfo'])
                reason = '{} : {}'.format(info['status'], info['message'])
            except KeyError:
                reason = 'unknown reason'
            raise exceptions.NetworkError(
                'image upload failed: {}'.format(reason))
        return response['sessionStatus']
Esempio n. 12
0
def otr_monkeypatched_adduser(self,
                              conversation_id,
                              chat_id_list,
                              otr_status=None):
    if otr_status is None:
        otr_status = OffTheRecordStatus.ON_THE_RECORD  # default
        try:
            if not bot.conversations.catalog[conversation_id]["history"]:
                otr_status = OffTheRecordStatus.OFF_THE_RECORD
        except KeyError:
            logger.warning("missing history flag: {}".format(conversation_id))
    logger.debug("hangups.client.Client.adduser, convid={} OTR={}".format(
        conversation_id, otr_status))

    # https://github.com/tdryer/hangups/blob/5ca47c7497c1456e99cef0f8d3dc5fc8c3ffe9df/hangups/client.py#L858
    """Add user to existing conversation.
    conversation_id must be a valid conversation ID.
    chat_id_list is list of users which should be invited to conversation.
    Raises hangups.NetworkError if the request fails.
    """
    client_generated_id = random.randint(0, 2**32)
    body = [
        self._get_request_header(), None,
        [[str(chat_id), None, None, "unknown", None, []]
         for chat_id in chat_id_list], None,
        [[conversation_id], client_generated_id, otr_status.value, None, 4]
    ]

    res = yield from self._request('conversations/adduser', body)
    # can return 200 but still contain an error
    res = json.loads(res.body.decode())
    res_status = res['response_header']['status']
    if res_status != 'OK':
        raise exceptions.NetworkError(
            'Unexpected status: {}'.format(res_status))
    return res
Esempio n. 13
0
    def adduser(self, conversation_id, chat_id_list):
        """Add user to existing conversation.

        conversation_id must be a valid conversation ID.
        chat_id_list is list of users which should be invited to conversation.

        Raises hangups.NetworkError if the request fails.
        """
        client_generated_id = random.randint(0, 2**32)
        body = [
            self._get_request_header(), None,
            [[str(chat_id), None, None, "unknown", None, []]
             for chat_id in chat_id_list], None,
            [[conversation_id], client_generated_id, 2, None, 4]
        ]

        res = yield from self._request('conversations/adduser', body)
        # can return 200 but still contain an error
        res = json.loads(res.body.decode())
        res_status = res['response_header']['status']
        if res_status != 'OK':
            raise exceptions.NetworkError(
                'Unexpected status: {}'.format(res_status))
        return res
Esempio n. 14
0
    def upload_image(self, image_file, filename=None, *,
                     return_uploaded_image=False):
        """Upload an image that can be later attached to a chat message.

        Args:
            image_file: A file-like object containing an image.
            filename (str): (optional) Custom name for the uploaded file.
            return_uploaded_image (bool): (optional) If True, return
                :class:`.UploadedImage` instead of image ID. Defaults to False.

        Raises:
            hangups.NetworkError: If the upload request failed.

        Returns:
            :class:`.UploadedImage` instance, or ID of the uploaded image.
        """
        image_filename = filename or os.path.basename(image_file.name)
        image_data = image_file.read()

        # request an upload URL
        res = yield from self._base_request(
            IMAGE_UPLOAD_URL,
            'application/x-www-form-urlencoded;charset=UTF-8', 'json',
            json.dumps({
                "protocolVersion": "0.8",
                "createSessionRequest": {
                    "fields": [{
                        "external": {
                            "name": "file",
                            "filename": image_filename,
                            "put": {},
                            "size": len(image_data)
                        }
                    }]
                }
            })
        )

        try:
            upload_url = self._get_upload_session_status(res)[
                'externalFieldTransfers'
            ][0]['putInfo']['url']
        except KeyError:
            raise exceptions.NetworkError(
                'image upload failed: can not acquire an upload url'
            )

        # upload the image data using the upload_url to get the upload info
        res = yield from self._base_request(
            upload_url, 'application/octet-stream', 'json', image_data
        )

        try:
            raw_info = (
                self._get_upload_session_status(res)['additionalInfo']
                ['uploader_service.GoogleRupioAdditionalInfo']
                ['completionInfo']['customerSpecificInfo']
            )
            image_id = raw_info['photoid']
            url = raw_info['url']
        except KeyError:
            raise exceptions.NetworkError(
                'image upload failed: can not fetch upload info'
            )

        result = UploadedImage(image_id=image_id, url=url)
        return result if return_uploaded_image else result.image_id
Esempio n. 15
0
    def _longpoll_request(self):
        """Open a long-polling request and receive arrays.

        This method uses keep-alive to make re-opening the request faster, but
        the remote server will set the "Connection: close" header once an hour.

        Raises hangups.NetworkError or UnknownSIDError.
        """
        params = {
            'VER': 8,  # channel protocol version
            'gsessionid': self._gsessionid_param,
            'RID': 'rpc',  # request identifier
            't': 1,  # trial
            'SID': self._sid_param,  # session ID
            'CI': 0,  # 0 if streaming/chunked requests should be used
            'ctype': 'hangouts',  # client type
            'TYPE': 'xmlhttp',  # type of request
        }
        headers = get_authorization_headers(self._cookies['SAPISID'])
        logger.info('Opening new long-polling request')
        try:
            res = yield from asyncio.wait_for(aiohttp.request(
                'get', CHANNEL_URL_PREFIX.format('channel/bind'),
                params=params, cookies=self._cookies, headers=headers,
                connector=self._connector
            ), CONNECT_TIMEOUT)
        except asyncio.TimeoutError:
            raise exceptions.NetworkError('Request timed out')
        except aiohttp.ClientError as e:
            raise exceptions.NetworkError('Request connection error: {}'
                                          .format(e))
        except aiohttp.ServerDisconnectedError as e:
            raise exceptions.NetworkError('Server disconnected error: {}'
                                          .format(e))
        if res.status == 400 and res.reason == 'Unknown SID':
            raise UnknownSIDError('SID became invalid')
        elif res.status != 200:
            raise exceptions.NetworkError(
                'Request return unexpected status: {}: {}'
                .format(res.status, res.reason)
            )
        while True:
            try:
                chunk = yield from asyncio.wait_for(
                    res.content.read(MAX_READ_BYTES), PUSH_TIMEOUT
                )
            except asyncio.TimeoutError:
                raise exceptions.NetworkError('Request timed out')
            except aiohttp.ClientError as e:
                raise exceptions.NetworkError('Request connection error: {}'
                                              .format(e))
            except aiohttp.ServerDisconnectedError as e:
                raise exceptions.NetworkError('Server disconnected error: {}'
                                              .format(e))
            except asyncio.CancelledError:
                # Prevent ResourceWarning when channel is disconnected.
                res.close()
                raise
            if chunk:
                yield from self._on_push_data(chunk)
            else:
                # Close the response to allow the connection to be reused for
                # the next request.
                res.close()
                break