Exemple #1
0
    def update_status(self, status, duration):
        """
        Updates our status from a twilio status string
        """
        if status == 'queued':
            self.status = QUEUED
        elif status == 'ringing':
            self.status = RINGING
        elif status == 'no-answer':
            self.status = NO_ANSWER
        elif status == 'in-progress':
            if self.status != IN_PROGRESS:
                self.started_on = timezone.now()
            self.status = IN_PROGRESS
        elif status == 'completed':
            if self.contact.is_test:
                run = FlowRun.objects.filter(call=self)
                if run:
                    ActionLog.create(run[0], _("Call ended."))
            self.status = COMPLETED
        elif status == 'busy':
            self.status = BUSY
        elif status == 'failed':
            self.status = FAILED
        elif status == 'canceled':
            self.status = CANCELED

        self.duration = duration
Exemple #2
0
    def update_status(self, status, duration, channel_type):
        """
        Updates our status from a provide call status string

        """
        from temba.flows.models import FlowRun, ActionLog

        previous_status = self.status
        ivr_protocol = Channel.get_type_from_code(channel_type).ivr_protocol

        if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML:
            if status == 'queued':
                self.status = self.QUEUED
            elif status == 'ringing':
                self.status = self.RINGING
            elif status == 'no-answer':
                self.status = self.NO_ANSWER
            elif status == 'in-progress':
                if self.status != self.IN_PROGRESS:
                    self.started_on = timezone.now()
                self.status = self.IN_PROGRESS
            elif status == 'completed':
                if self.contact.is_test:
                    run = FlowRun.objects.filter(connection=self)
                    if run:
                        ActionLog.create(run[0], _("Call ended."))
                self.status = self.COMPLETED
            elif status == 'busy':
                self.status = self.BUSY
            elif status == 'failed':
                self.status = self.FAILED
            elif status == 'canceled':
                self.status = self.CANCELED

        elif ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO:
            if status in ('ringing', 'started'):
                self.status = self.RINGING
            elif status == 'answered':
                self.status = self.IN_PROGRESS
            elif status == 'completed':
                self.status = self.COMPLETED
            elif status == 'failed':
                self.status = self.FAILED
            elif status in ('rejected', 'busy'):
                self.status = self.BUSY
            elif status in ('unanswered', 'timeout'):
                self.status = self.NO_ANSWER

        # if we are done, mark our ended time
        if self.status in ChannelSession.DONE:
            self.ended_on = timezone.now()

        if duration is not None:
            self.duration = duration

        # if we are moving into IN_PROGRESS, make sure our runs have proper expirations
        if previous_status in [self.QUEUED, self.PENDING] and self.status in [self.IN_PROGRESS, self.RINGING]:
            runs = FlowRun.objects.filter(connection=self, is_active=True, expires_on=None)
            for run in runs:
                run.update_expiration()
Exemple #3
0
    def do_start_call(self, qs=None):
        client = self.channel.get_ivr_client()
        if client:
            try:
                url = "https://%s%s" % (settings.TEMBA_HOST, reverse('ivr.ivrcall_handle', args=[self.pk]))
                if qs:
                    url = "%s?%s" % (url, qs)

                tel = None

                # if we are working with a test contact, look for user settings
                if self.contact.is_test:
                    user_settings = self.created_by.get_settings()
                    if user_settings.tel:
                        tel = user_settings.tel
                        run = FlowRun.objects.filter(call=self)
                        if run:
                            ActionLog.create(run[0], "Placing test call to %s" % user_settings.get_tel_formatted())
                if not tel:
                    tel_urn = self.contact_urn
                    tel = tel_urn.path

                client.start_call(self, to=tel, from_=self.channel.address, status_callback=url)

            except Exception:  # pragma: no cover
                import traceback
                traceback.print_exc()
                self.status = FAILED
                self.save()
Exemple #4
0
    def update_status(self, status: str, duration: float, channel_type: str):
        """
        Updates our status from a provide call status string

        """
        if not status:
            raise ValueError(
                f"IVR Call status must be defined, got: '{status}'")

        previous_status = self.status

        from temba.flows.models import FlowRun, ActionLog

        ivr_protocol = Channel.get_type_from_code(channel_type).ivr_protocol
        if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML:
            self.status = self.derive_ivr_status_twiml(status, previous_status)
        elif ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO:
            self.status = self.derive_ivr_status_nexmo(status, previous_status)
        else:  # pragma: no cover
            raise ValueError(f"Unhandled IVR protocol: {ivr_protocol}")

        # if we are in progress, mark our start time
        if self.status == self.IN_PROGRESS and previous_status != self.IN_PROGRESS:
            self.started_on = timezone.now()

        # if we are done, mark our ended time
        if self.status in ChannelSession.DONE:
            self.ended_on = timezone.now()

            if self.contact.is_test:
                run = FlowRun.objects.filter(connection=self)
                if run:
                    ActionLog.create(run[0], _("Call ended."))

        if self.status in ChannelSession.RETRY_CALL and previous_status not in ChannelSession.RETRY_CALL:
            flow = self.get_flow()
            backoff_minutes = flow.metadata.get("ivr_retry",
                                                IVRCall.RETRY_BACKOFF_MINUTES)

            self.schedule_call_retry(backoff_minutes)

        if duration is not None:
            self.duration = duration

        # if we are moving into IN_PROGRESS, make sure our runs have proper expirations
        if previous_status in (self.PENDING, self.QUEUED,
                               self.WIRED) and self.status in (
                                   self.IN_PROGRESS,
                                   self.RINGING,
                               ):
            runs = FlowRun.objects.filter(connection=self,
                                          is_active=True,
                                          expires_on=None)
            for run in runs:
                run.update_expiration()
Exemple #5
0
    def update_status(self, status: str, duration: float, channel_type: str):
        """
        Updates our status from a provide call status string

        """
        if not status:
            raise ValueError(f"IVR Call status must be defined, got: '{status}'")

        previous_status = self.status

        from temba.flows.models import FlowRun, ActionLog

        ivr_protocol = Channel.get_type_from_code(channel_type).ivr_protocol
        if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML:
            self.status = self.derive_ivr_status_twiml(status, previous_status)
        elif ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO:
            self.status = self.derive_ivr_status_nexmo(status, previous_status)
        else:  # pragma: no cover
            raise ValueError(f"Unhandled IVR protocol: {ivr_protocol}")

        # if we are in progress, mark our start time
        if self.status == self.IN_PROGRESS and previous_status != self.IN_PROGRESS:
            self.started_on = timezone.now()

        # if we are done, mark our ended time
        if self.status in ChannelSession.DONE:
            self.ended_on = timezone.now()

            if self.contact.is_test:
                run = FlowRun.objects.filter(connection=self)
                if run:
                    ActionLog.create(run[0], _("Call ended."))

        if self.status in ChannelSession.RETRY_CALL and previous_status not in ChannelSession.RETRY_CALL:
            flow = self.get_flow()
            backoff_minutes = flow.metadata.get("ivr_retry", IVRCall.RETRY_BACKOFF_MINUTES)

            self.schedule_call_retry(backoff_minutes)

        if duration is not None:
            self.duration = duration

        # if we are moving into IN_PROGRESS, make sure our runs have proper expirations
        if previous_status in (self.PENDING, self.QUEUED, self.WIRED) and self.status in (
            self.IN_PROGRESS,
            self.RINGING,
        ):
            runs = FlowRun.objects.filter(connection=self, is_active=True, expires_on=None)
            for run in runs:
                run.update_expiration()
Exemple #6
0
    def update_status(self, status, duration, channel_type):
        """
        Updates our status from a provide call status string

        """
        from temba.flows.models import FlowRun, ActionLog
        if channel_type in Channel.TWIML_CHANNELS:
            if status == 'queued':
                self.status = self.QUEUED
            elif status == 'ringing':
                self.status = self.RINGING
            elif status == 'no-answer':
                self.status = self.NO_ANSWER
            elif status == 'in-progress':
                if self.status != self.IN_PROGRESS:
                    self.started_on = timezone.now()
                self.status = self.IN_PROGRESS
            elif status == 'completed':
                if self.contact.is_test:
                    run = FlowRun.objects.filter(session=self)
                    if run:
                        ActionLog.create(run[0], _("Call ended."))
                self.status = self.COMPLETED
            elif status == 'busy':
                self.status = self.BUSY
            elif status == 'failed':
                self.status = self.FAILED
            elif status == 'canceled':
                self.status = self.CANCELED

        elif channel_type in Channel.NCCO_CHANNELS:
            if status in ('ringing', 'started'):
                self.status = self.RINGING
            elif status == 'answered':
                self.status = self.IN_PROGRESS
            elif status == 'completed':
                self.status = self.COMPLETED
            elif status == 'failed':
                self.status = self.FAILED
            elif status in ('rejected', 'busy'):
                self.status = self.BUSY
            elif status in ('unanswered', 'timeout'):
                self.status = self.NO_ANSWER

        # if we are done, mark our ended time
        if self.status in ChannelSession.DONE:
            self.ended_on = timezone.now()

        if duration is not None:
            self.duration = duration
Exemple #7
0
    def do_start_call(self, qs=None):
        client = self.channel.get_ivr_client()
        domain = self.channel.callback_domain

        from temba.ivr.clients import IVRException
        from temba.flows.models import ActionLog, FlowRun

        if client:
            try:
                url = "https://%s%s" % (
                    domain, reverse("ivr.ivrcall_handle", args=[self.pk]))
                if qs:  # pragma: no cover
                    url = "%s?%s" % (url, qs)

                tel = None

                # if we are working with a test contact, look for user settings
                if self.contact.is_test:
                    user_settings = self.created_by.get_settings()
                    if user_settings.tel:
                        tel = user_settings.tel
                        run = FlowRun.objects.filter(connection=self)
                        if run:
                            ActionLog.create(
                                run[0], "Placing test call to %s" %
                                user_settings.get_tel_formatted())
                if not tel:
                    tel_urn = self.contact_urn
                    tel = tel_urn.path

                client.start_call(self,
                                  to=tel,
                                  from_=self.channel.address,
                                  status_callback=url)

            except IVRException as e:
                import traceback

                traceback.print_exc()

                if self.contact.is_test:
                    run = FlowRun.objects.filter(connection=self)
                    ActionLog.create(run[0], "Call ended. %s" % str(e))

            except Exception as e:  # pragma: no cover
                import traceback

                traceback.print_exc()

                self.status = self.FAILED
                self.save()

                if self.contact.is_test:
                    run = FlowRun.objects.filter(connection=self)
                    ActionLog.create(run[0], "Call ended.")
Exemple #8
0
    def do_start_call(self, qs=None):
        client = self.channel.get_ivr_client()
        domain = self.channel.callback_domain

        from temba.ivr.clients import IVRException
        from temba.flows.models import ActionLog, FlowRun

        if client:
            try:
                url = "https://%s%s" % (domain, reverse("ivr.ivrcall_handle", args=[self.pk]))
                if qs:  # pragma: no cover
                    url = "%s?%s" % (url, qs)

                tel = None

                # if we are working with a test contact, look for user settings
                if self.contact.is_test:
                    user_settings = self.created_by.get_settings()
                    if user_settings.tel:
                        tel = user_settings.tel
                        run = FlowRun.objects.filter(connection=self)
                        if run:
                            ActionLog.create(run[0], "Placing test call to %s" % user_settings.get_tel_formatted())
                if not tel:
                    tel_urn = self.contact_urn
                    tel = tel_urn.path

                client.start_call(self, to=tel, from_=self.channel.address, status_callback=url)

            except IVRException as e:
                import traceback

                traceback.print_exc()

                if self.contact.is_test:
                    run = FlowRun.objects.filter(connection=self)
                    ActionLog.create(run[0], "Call ended. %s" % str(e))

            except Exception as e:  # pragma: no cover
                import traceback

                traceback.print_exc()

                ChannelLog.log_ivr_interaction(
                    self, "Call failed unexpectedly", HttpEvent(method="INTERNAL", url=None, response_body=str(e))
                )

                self.status = self.FAILED
                self.save()

                if self.contact.is_test:
                    run = FlowRun.objects.filter(connection=self)
                    ActionLog.create(run[0], "Call ended.")
Exemple #9
0
    def trigger_flow_event(cls,
                           webhook_url,
                           flow,
                           run,
                           node_uuid,
                           contact,
                           event,
                           action='POST',
                           resthook=None):
        org = flow.org
        api_user = get_api_user()
        json_time = datetime_to_str(timezone.now())

        # get the results for this contact
        results = flow.get_results(contact)
        values = []

        if results and results[0]:
            values = results[0]['values']
            for value in values:
                value['time'] = datetime_to_str(value['time'])
                value['value'] = unicode(value['value'])

        # if the action is on the first node
        # we might not have an sms (or channel) yet
        channel = None
        text = None
        contact_urn = contact.get_urn()

        if event:
            text = event.text
            channel = event.channel
            contact_urn = event.contact_urn

        if channel:
            channel_id = channel.pk
        else:
            channel_id = -1

        steps = []
        for step in run.steps.prefetch_related(
                'messages', 'broadcasts').order_by('arrived_on'):
            steps.append(
                dict(type=step.step_type,
                     node=step.step_uuid,
                     arrived_on=datetime_to_str(step.arrived_on),
                     left_on=datetime_to_str(step.left_on),
                     text=step.get_text(),
                     value=step.rule_value))

        data = dict(channel=channel_id,
                    relayer=channel_id,
                    flow=flow.id,
                    flow_name=flow.name,
                    flow_base_language=flow.base_language,
                    run=run.id,
                    text=text,
                    step=unicode(node_uuid),
                    phone=contact.get_urn_display(org=org,
                                                  scheme=TEL_SCHEME,
                                                  formatted=False),
                    contact=contact.uuid,
                    urn=unicode(contact_urn),
                    values=json.dumps(values),
                    steps=json.dumps(steps),
                    time=json_time)

        if not action:
            action = 'POST'

        webhook_event = WebHookEvent.objects.create(org=org,
                                                    event=FLOW,
                                                    channel=channel,
                                                    data=json.dumps(data),
                                                    try_count=1,
                                                    action=action,
                                                    resthook=resthook,
                                                    created_by=api_user,
                                                    modified_by=api_user)

        status_code = -1
        message = "None"
        body = None

        # webhook events fire immediately since we need the results back
        try:
            # only send webhooks when we are configured to, otherwise fail
            if not settings.SEND_WEBHOOKS:
                raise Exception(
                    "!! Skipping WebHook send, SEND_WEBHOOKS set to False")

            # no url, bail!
            if not webhook_url:
                raise Exception("No webhook_url specified, skipping send")

            # some hosts deny generic user agents, use Temba as our user agent
            if action == 'GET':
                response = requests.get(webhook_url,
                                        headers=TEMBA_HEADERS,
                                        timeout=10)
            else:
                response = requests.post(webhook_url,
                                         data=data,
                                         headers=TEMBA_HEADERS,
                                         timeout=10)

            response_text = response.text
            body = response.text
            status_code = response.status_code

            if response.status_code == 200 or response.status_code == 201:
                try:
                    response_json = json.loads(response_text)

                    # only update if we got a valid JSON dictionary or list
                    if not isinstance(response_json, dict) and not isinstance(
                            response_json, list):
                        raise ValueError(
                            "Response must be a JSON dictionary or list, ignoring response."
                        )

                    run.update_fields(response_json)
                    message = "Webhook called successfully."
                except ValueError as e:
                    message = "Response must be a JSON dictionary, ignoring response."

                webhook_event.status = COMPLETE
            else:
                webhook_event.status = FAILED
                message = "Got non 200 response (%d) from webhook." % response.status_code
                raise Exception("Got non 200 response (%d) from webhook." %
                                response.status_code)

        except Exception as e:
            import traceback
            traceback.print_exc()

            webhook_event.status = FAILED
            message = "Error calling webhook: %s" % unicode(e)

        finally:
            webhook_event.save()

            # make sure our message isn't too long
            if message:
                message = message[:255]

            result = WebHookResult.objects.create(event=webhook_event,
                                                  url=webhook_url,
                                                  status_code=status_code,
                                                  body=body,
                                                  message=message,
                                                  data=urlencode(data,
                                                                 doseq=True),
                                                  created_by=api_user,
                                                  modified_by=api_user)

            # if this is a test contact, add an entry to our action log
            if run.contact.is_test:
                from temba.flows.models import ActionLog
                log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % (
                    reverse('api.log_read', args=[webhook_event.pk
                                                  ]), status_code)
                ActionLog.create(run, log_txt, safe=True)

        return result
Exemple #10
0
    def trigger_flow_webhook(cls,
                             run,
                             webhook_url,
                             node_uuid,
                             msg,
                             action='POST',
                             resthook=None,
                             headers=None):

        flow = run.flow
        contact = run.contact
        org = flow.org
        channel = msg.channel if msg else None
        contact_urn = msg.contact_urn if msg else contact.get_urn()

        contact_dict = dict(uuid=contact.uuid, name=contact.name)
        if contact_urn:
            contact_dict['urn'] = contact_urn.urn

        post_data = {
            'contact':
            contact_dict,
            'flow':
            dict(name=flow.name, uuid=flow.uuid),
            'path':
            run.get_path(),
            'results':
            run.get_results(),
            'run':
            dict(uuid=six.text_type(run.uuid),
                 created_on=run.created_on.isoformat())
        }

        if msg and msg.id > 0:
            post_data['input'] = dict(
                urn=msg.contact_urn.urn if msg.contact_urn else None,
                text=msg.text,
                attachments=(msg.attachments or []))

        if channel:
            post_data['channel'] = dict(name=channel.name, uuid=channel.uuid)

        api_user = get_api_user()
        if not action:  # pragma: needs cover
            action = 'POST'

        webhook_event = cls.objects.create(org=org,
                                           event=cls.TYPE_FLOW,
                                           channel=channel,
                                           data=json.dumps(post_data),
                                           run=run,
                                           try_count=1,
                                           action=action,
                                           resthook=resthook,
                                           created_by=api_user,
                                           modified_by=api_user)

        status_code = -1
        message = "None"
        body = None

        start = time.time()

        # webhook events fire immediately since we need the results back
        try:
            # no url, bail!
            if not webhook_url:
                raise ValueError("No webhook_url specified, skipping send")

            # only send webhooks when we are configured to, otherwise fail
            if settings.SEND_WEBHOOKS:
                requests_headers = http_headers(extra=headers)

                # some hosts deny generic user agents, use Temba as our user agent
                if action == 'GET':
                    response = requests.get(webhook_url,
                                            headers=requests_headers,
                                            timeout=10)
                else:
                    requests_headers['Content-type'] = 'application/json'
                    response = requests.post(webhook_url,
                                             data=json.dumps(post_data),
                                             headers=requests_headers,
                                             timeout=10)

                body = response.text
                if body:
                    body = body.strip()
                status_code = response.status_code
            else:
                print("!! Skipping WebHook send, SEND_WEBHOOKS set to False")
                body = 'Skipped actual send'
                status_code = 200

            # process the webhook response
            try:
                response_json = json.loads(body, object_pairs_hook=OrderedDict)

                # only update if we got a valid JSON dictionary or list
                if not isinstance(response_json, dict) and not isinstance(
                        response_json, list):
                    raise ValueError(
                        "Response must be a JSON dictionary or list, ignoring response."
                    )

                run.update_fields(response_json)
                message = "Webhook called successfully."
            except ValueError:
                message = "Response must be a JSON dictionary, ignoring response."

            if 200 <= status_code < 300:
                webhook_event.status = cls.STATUS_COMPLETE
            else:
                webhook_event.status = cls.STATUS_FAILED
                message = "Got non 200 response (%d) from webhook." % response.status_code
                raise Exception("Got non 200 response (%d) from webhook." %
                                response.status_code)

        except Exception as e:
            import traceback
            traceback.print_exc()

            webhook_event.status = cls.STATUS_FAILED
            message = "Error calling webhook: %s" % six.text_type(e)

        finally:
            webhook_event.save(update_fields=('status', ))

            # make sure our message isn't too long
            if message:
                message = message[:255]

            request_time = (time.time() - start) * 1000

            contact = None
            if webhook_event.run:
                contact = webhook_event.run.contact

            result = WebHookResult.objects.create(event=webhook_event,
                                                  contact=contact,
                                                  url=webhook_url,
                                                  status_code=status_code,
                                                  body=body,
                                                  message=message,
                                                  data=json.dumps(post_data),
                                                  request_time=request_time,
                                                  created_by=api_user,
                                                  modified_by=api_user)

            # if this is a test contact, add an entry to our action log
            if run.contact.is_test:
                log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % (
                    reverse('api.log_read', args=[webhook_event.pk
                                                  ]), status_code)
                ActionLog.create(run, log_txt, safe=True)

        return result
Exemple #11
0
    def trigger_flow_event(cls, webhook_url, flow, run, node, contact, event, action="POST"):
        org = flow.org
        api_user = get_api_user()

        # no-op if no webhook configured
        if not webhook_url:
            return

        json_time = datetime_to_str(timezone.now())

        # get the results for this contact
        results = flow.get_results(contact)
        values = []

        if results and results[0]:
            values = results[0]["values"]
            for value in values:
                value["time"] = datetime_to_str(value["time"])
                value["value"] = unicode(value["value"])

        # if the action is on the first node
        # we might not have an sms (or channel) yet
        channel = None
        text = None

        if event:
            text = event.text
            channel = event.channel

        if channel:
            channel_id = channel.pk
        else:
            channel_id = -1

        steps = []
        for step in run.steps.all().order_by("arrived_on"):
            steps.append(
                dict(
                    type=step.step_type,
                    node=step.step_uuid,
                    arrived_on=datetime_to_str(step.arrived_on),
                    left_on=datetime_to_str(step.left_on),
                    text=step.get_text(),
                    value=step.rule_value,
                )
            )

        data = dict(
            channel=channel_id,
            relayer=channel_id,
            flow=flow.id,
            run=run.id,
            text=text,
            step=unicode(node.uuid),
            phone=contact.get_urn_display(org=org, scheme=TEL_SCHEME, full=True),
            values=json.dumps(values),
            steps=json.dumps(steps),
            time=json_time,
        )

        if not action:
            action = "POST"

        webhook_event = WebHookEvent.objects.create(
            org=org,
            event=FLOW,
            channel=channel,
            data=json.dumps(data),
            try_count=1,
            action=action,
            created_by=api_user,
            modified_by=api_user,
        )

        status_code = -1
        message = "None"
        body = None

        # webhook events fire immediately since we need the results back
        try:
            # only send webhooks when we are configured to, otherwise fail
            if not settings.SEND_WEBHOOKS:
                raise Exception("!! Skipping WebHook send, SEND_WEBHOOKS set to False")

            # some hosts deny generic user agents, use Temba as our user agent
            if action == "GET":
                response = requests.get(webhook_url, headers=TEMBA_HEADERS, timeout=10)
            else:
                response = requests.post(webhook_url, data=data, headers=TEMBA_HEADERS, timeout=10)

            response_text = response.text
            body = response.text
            status_code = response.status_code

            if response.status_code == 200 or response.status_code == 201:
                try:
                    response_json = json.loads(response_text)

                    # only update if we got a valid JSON dictionary
                    if not isinstance(response_json, dict):
                        raise ValueError("Response must be a JSON dictionary, ignoring response.")

                    run.update_fields(response_json)
                    message = "Webhook called successfully."
                except ValueError as e:
                    message = "Response must be a JSON dictionary, ignoring response."

                webhook_event.status = COMPLETE
            else:
                webhook_event.status = FAILED
                message = "Got non 200 response (%d) from webhook." % response.status_code
                raise Exception("Got non 200 response (%d) from webhook." % response.status_code)

        except Exception as e:
            import traceback

            traceback.print_exc()

            webhook_event.status = FAILED
            message = "Error calling webhook: %s" % unicode(e)

        finally:
            webhook_event.save()
            result = WebHookResult.objects.create(
                event=webhook_event,
                url=webhook_url,
                status_code=status_code,
                body=body,
                message=message,
                data=urlencode(data, doseq=True),
                created_by=api_user,
                modified_by=api_user,
            )

            # if this is a test contact, add an entry to our action log
            if run.contact.is_test:
                from temba.flows.models import ActionLog

                log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % (
                    reverse("api.log_read", args=[webhook_event.pk]),
                    status_code,
                )
                ActionLog.create(run, log_txt, safe=True)

        return result
Exemple #12
0
    def trigger_flow_webhook(cls, run, webhook_url, ruleset, msg, action="POST", resthook=None, headers=None):

        flow = run.flow
        contact = run.contact
        org = flow.org
        channel = msg.channel if msg else None
        contact_urn = msg.contact_urn if (msg and msg.contact_urn) else contact.get_urn()

        contact_dict = dict(uuid=contact.uuid, name=contact.name)
        if contact_urn:
            contact_dict["urn"] = contact_urn.urn

        post_data = {
            "contact": contact_dict,
            "flow": dict(name=flow.name, uuid=flow.uuid, revision=flow.revisions.order_by("revision").last().revision),
            "path": run.path,
            "results": run.results,
            "run": dict(uuid=str(run.uuid), created_on=run.created_on.isoformat()),
        }

        if msg and msg.id > 0:
            post_data["input"] = dict(
                urn=msg.contact_urn.urn if msg.contact_urn else None,
                text=msg.text,
                attachments=(msg.attachments or []),
            )

        if channel:
            post_data["channel"] = dict(name=channel.name, uuid=channel.uuid)

        api_user = get_api_user()
        if not action:  # pragma: needs cover
            action = "POST"

        webhook_event = cls.objects.create(
            org=org,
            event=cls.TYPE_FLOW,
            channel=channel,
            data=post_data,
            run=run,
            try_count=1,
            action=action,
            resthook=resthook,
            created_by=api_user,
            modified_by=api_user,
        )

        status_code = -1
        message = "None"
        body = None

        start = time.time()

        # webhook events fire immediately since we need the results back
        try:
            # no url, bail!
            if not webhook_url:
                raise ValueError("No webhook_url specified, skipping send")

            # only send webhooks when we are configured to, otherwise fail
            if settings.SEND_WEBHOOKS:
                requests_headers = http_headers(extra=headers)

                # some hosts deny generic user agents, use Temba as our user agent
                if action == "GET":
                    response = requests.get(webhook_url, headers=requests_headers, timeout=10)
                else:
                    requests_headers["Content-type"] = "application/json"
                    response = requests.post(
                        webhook_url, data=json.dumps(post_data), headers=requests_headers, timeout=10
                    )

                body = response.text
                if body:
                    body = body.strip()
                status_code = response.status_code
            else:
                print("!! Skipping WebHook send, SEND_WEBHOOKS set to False")
                body = "Skipped actual send"
                status_code = 200

            if ruleset:
                run.update_fields({Flow.label_to_slug(ruleset.label): body}, do_save=False)
            new_extra = {}

            # process the webhook response
            try:
                response_json = json.loads(body)

                # only update if we got a valid JSON dictionary or list
                if not isinstance(response_json, dict) and not isinstance(response_json, list):
                    raise ValueError("Response must be a JSON dictionary or list, ignoring response.")

                new_extra = response_json
                message = "Webhook called successfully."
            except ValueError:
                message = "Response must be a JSON dictionary, ignoring response."

            run.update_fields(new_extra)

            if 200 <= status_code < 300:
                webhook_event.status = cls.STATUS_COMPLETE
            else:
                webhook_event.status = cls.STATUS_FAILED
                message = "Got non 200 response (%d) from webhook." % response.status_code
                raise Exception("Got non 200 response (%d) from webhook." % response.status_code)

        except Exception as e:
            import traceback

            traceback.print_exc()

            webhook_event.status = cls.STATUS_FAILED
            message = "Error calling webhook: %s" % str(e)

        finally:
            webhook_event.save(update_fields=("status",))

            # make sure our message isn't too long
            if message:
                message = message[:255]

            request_time = (time.time() - start) * 1000

            contact = None
            if webhook_event.run:
                contact = webhook_event.run.contact

            result = WebHookResult.objects.create(
                event=webhook_event,
                contact=contact,
                url=webhook_url,
                status_code=status_code,
                body=body,
                message=message,
                data=post_data,
                request_time=request_time,
                created_by=api_user,
                modified_by=api_user,
            )

            # if this is a test contact, add an entry to our action log
            if run.contact.is_test:  # pragma: no cover
                log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % (
                    reverse("api.log_read", args=[webhook_event.pk]),
                    status_code,
                )
                ActionLog.create(run, log_txt, safe=True)

        return result
Exemple #13
0
    def do_start_call(self, qs=None):
        client = self.channel.get_ivr_client()
        domain = self.channel.callback_domain

        from temba.ivr.clients import IVRException
        from temba.flows.models import ActionLog, FlowRun

        if client and domain:
            try:
                url = "https://%s%s" % (domain, reverse("ivr.ivrcall_handle", args=[self.pk]))
                if qs:  # pragma: no cover
                    url = "%s?%s" % (url, qs)

                tel = None

                # if we are working with a test contact, look for user settings
                if self.contact.is_test:
                    user_settings = self.created_by.get_settings()
                    if user_settings.tel:
                        tel = user_settings.tel
                        run = FlowRun.objects.filter(connection=self)
                        if run:
                            ActionLog.create(run[0], "Placing test call to %s" % user_settings.get_tel_formatted())
                if not tel:
                    tel_urn = self.contact_urn
                    tel = tel_urn.path

                client.start_call(self, to=tel, from_=self.channel.address, status_callback=url)

            except IVRException as e:
                import traceback

                traceback.print_exc()

                if self.contact.is_test:
                    run = FlowRun.objects.filter(connection=self)
                    ActionLog.create(run[0], "Call ended. %s" % str(e))

            except Exception as e:  # pragma: no cover
                import traceback

                traceback.print_exc()

                ChannelLog.log_ivr_interaction(
                    self, "Call failed unexpectedly", HttpEvent(method="INTERNAL", url=None, response_body=str(e))
                )

                self.status = self.FAILED
                self.save(update_fields=("status",))

                if self.contact.is_test:
                    run = FlowRun.objects.filter(connection=self)
                    ActionLog.create(run[0], "Call ended.")

        # client or domain are not known
        else:
            ChannelLog.log_ivr_interaction(
                self,
                "Unknown client or domain",
                HttpEvent(method="INTERNAL", url=None, response_body=f"client={client} domain={domain}"),
            )

            self.status = self.FAILED
            self.save(update_fields=("status",))
Exemple #14
0
    def trigger_flow_event(cls, run, webhook_url, node_uuid, msg, action='POST', resthook=None, header=None):
        flow = run.flow
        org = flow.org
        contact = run.contact
        api_user = get_api_user()
        json_time = datetime_to_str(timezone.now())

        # get the results for this contact
        results = run.flow.get_results(run.contact)
        values = []

        if results and results[0]:
            values = results[0]['values']
            for value in values:
                value['time'] = datetime_to_str(value['time'])
                value['value'] = six.text_type(value['value'])

        if msg:
            text = msg.text
            attachments = msg.get_attachments()
            channel = msg.channel
            contact_urn = msg.contact_urn
        else:
            # if the action is on the first node we might not have an sms (or channel) yet
            channel = None
            text = None
            attachments = []
            contact_urn = contact.get_urn()

        steps = []
        for step in run.steps.prefetch_related('messages', 'broadcasts').order_by('arrived_on'):
            steps.append(dict(type=step.step_type,
                              node=step.step_uuid,
                              arrived_on=datetime_to_str(step.arrived_on),
                              left_on=datetime_to_str(step.left_on),
                              text=step.get_text(),
                              value=step.rule_value))

        data = dict(channel=channel.id if channel else -1,
                    channel_uuid=channel.uuid if channel else None,
                    relayer=channel.id if channel else -1,
                    flow=flow.id,
                    flow_uuid=flow.uuid,
                    flow_name=flow.name,
                    flow_base_language=flow.base_language,
                    run=run.id,
                    text=text,
                    attachments=[a.url for a in attachments],
                    step=six.text_type(node_uuid),
                    phone=contact.get_urn_display(org=org, scheme=TEL_SCHEME, formatted=False),
                    contact=contact.uuid,
                    contact_name=contact.name,
                    urn=six.text_type(contact_urn),
                    values=json.dumps(values),
                    steps=json.dumps(steps),
                    time=json_time,
                    header=header)

        if not action:  # pragma: needs cover
            action = 'POST'

        webhook_event = cls.objects.create(org=org, event=cls.TYPE_FLOW, channel=channel, data=json.dumps(data),
                                           run=run, try_count=1, action=action, resthook=resthook,
                                           created_by=api_user, modified_by=api_user)

        status_code = -1
        message = "None"
        body = None

        start = time.time()

        # webhook events fire immediately since we need the results back
        try:
            # no url, bail!
            if not webhook_url:
                raise Exception("No webhook_url specified, skipping send")

            # only send webhooks when we are configured to, otherwise fail
            if settings.SEND_WEBHOOKS:

                requests_headers = TEMBA_HEADERS

                if header:
                    requests_headers.update(header)

                # some hosts deny generic user agents, use Temba as our user agent
                if action == 'GET':
                    response = requests.get(webhook_url, headers=requests_headers, timeout=10)
                else:
                    response = requests.post(webhook_url, data=data, headers=requests_headers, timeout=10)

                body = response.text
                if body:
                    body = body.strip()
                status_code = response.status_code
            else:
                print("!! Skipping WebHook send, SEND_WEBHOOKS set to False")
                body = 'Skipped actual send'
                status_code = 200

            # process the webhook response
            try:
                response_json = json.loads(body, object_pairs_hook=OrderedDict)

                # only update if we got a valid JSON dictionary or list
                if not isinstance(response_json, dict) and not isinstance(response_json, list):
                    raise ValueError("Response must be a JSON dictionary or list, ignoring response.")

                run.update_fields(response_json)
                message = "Webhook called successfully."
            except ValueError:
                message = "Response must be a JSON dictionary, ignoring response."

            if 200 <= status_code < 300:
                webhook_event.status = cls.STATUS_COMPLETE
            else:
                webhook_event.status = cls.STATUS_FAILED
                message = "Got non 200 response (%d) from webhook." % response.status_code
                raise Exception("Got non 200 response (%d) from webhook." % response.status_code)

        except Exception as e:
            import traceback
            traceback.print_exc()

            webhook_event.status = cls.STATUS_FAILED
            message = "Error calling webhook: %s" % six.text_type(e)

        finally:
            webhook_event.save()

            # make sure our message isn't too long
            if message:
                message = message[:255]

            request_time = (time.time() - start) * 1000

            contact = None
            if webhook_event.run:
                contact = webhook_event.run.contact

            result = WebHookResult.objects.create(event=webhook_event,
                                                  contact=contact,
                                                  url=webhook_url,
                                                  status_code=status_code,
                                                  body=body,
                                                  message=message,
                                                  data=urlencode(data, doseq=True),
                                                  request_time=request_time,
                                                  created_by=api_user,
                                                  modified_by=api_user)

            # if this is a test contact, add an entry to our action log
            if run.contact.is_test:
                log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % (reverse('api.log_read', args=[webhook_event.pk]), status_code)
                ActionLog.create(run, log_txt, safe=True)

        return result
Exemple #15
0
    def trigger_flow_webhook(cls,
                             run,
                             webhook_url,
                             ruleset,
                             msg,
                             action="POST",
                             resthook=None,
                             headers=None):

        flow = run.flow
        contact = run.contact
        org = flow.org
        channel = msg.channel if msg else None
        contact_urn = msg.contact_urn if (
            msg and msg.contact_urn) else contact.get_urn()

        contact_dict = dict(uuid=contact.uuid, name=contact.name)
        if contact_urn:
            contact_dict["urn"] = contact_urn.urn

        post_data = {
            "contact":
            contact_dict,
            "flow":
            dict(name=flow.name,
                 uuid=flow.uuid,
                 revision=flow.revisions.order_by("revision").last().revision),
            "path":
            run.path,
            "results":
            run.results,
            "run":
            dict(uuid=str(run.uuid), created_on=run.created_on.isoformat()),
        }

        if msg and msg.id > 0:
            post_data["input"] = dict(
                urn=msg.contact_urn.urn if msg.contact_urn else None,
                text=msg.text,
                attachments=(msg.attachments or []),
            )

        if channel:
            post_data["channel"] = dict(name=channel.name, uuid=channel.uuid)

        api_user = get_api_user()
        if not action:  # pragma: needs cover
            action = "POST"

        webhook_event = cls.objects.create(
            org=org,
            event=cls.TYPE_FLOW,
            channel=channel,
            data=post_data,
            run=run,
            try_count=1,
            action=action,
            resthook=resthook,
            created_by=api_user,
            modified_by=api_user,
        )

        status_code = -1
        message = "None"
        body = None

        start = time.time()

        # webhook events fire immediately since we need the results back
        try:
            # no url, bail!
            if not webhook_url:
                raise ValueError("No webhook_url specified, skipping send")

            # only send webhooks when we are configured to, otherwise fail
            if settings.SEND_WEBHOOKS:
                requests_headers = http_headers(extra=headers)

                # some hosts deny generic user agents, use Temba as our user agent
                if action == "GET":
                    response = requests.get(webhook_url,
                                            headers=requests_headers,
                                            timeout=10)
                else:
                    requests_headers["Content-type"] = "application/json"
                    response = requests.post(webhook_url,
                                             data=json.dumps(post_data),
                                             headers=requests_headers,
                                             timeout=10)

                body = response.text
                if body:
                    body = body.strip()
                status_code = response.status_code
            else:
                print("!! Skipping WebHook send, SEND_WEBHOOKS set to False")
                body = "Skipped actual send"
                status_code = 200

            if ruleset:
                run.update_fields({Flow.label_to_slug(ruleset.label): body},
                                  do_save=False)
            new_extra = {}

            # process the webhook response
            try:
                response_json = json.loads(body)

                # only update if we got a valid JSON dictionary or list
                if not isinstance(response_json, dict) and not isinstance(
                        response_json, list):
                    raise ValueError(
                        "Response must be a JSON dictionary or list, ignoring response."
                    )

                new_extra = response_json
                message = "Webhook called successfully."
            except ValueError:
                message = "Response must be a JSON dictionary, ignoring response."

            run.update_fields(new_extra)

            if 200 <= status_code < 300:
                webhook_event.status = cls.STATUS_COMPLETE
            else:
                webhook_event.status = cls.STATUS_FAILED
                message = "Got non 200 response (%d) from webhook." % response.status_code
                raise Exception("Got non 200 response (%d) from webhook." %
                                response.status_code)

        except Exception as e:
            import traceback

            traceback.print_exc()

            webhook_event.status = cls.STATUS_FAILED
            message = "Error calling webhook: %s" % str(e)

        finally:
            webhook_event.save(update_fields=("status", ))

            # make sure our message isn't too long
            if message:
                message = message[:255]

            request_time = (time.time() - start) * 1000

            contact = None
            if webhook_event.run:
                contact = webhook_event.run.contact

            result = WebHookResult.objects.create(
                event=webhook_event,
                contact=contact,
                url=webhook_url,
                status_code=status_code,
                body=body,
                message=message,
                data=post_data,
                request_time=request_time,
                created_by=api_user,
                modified_by=api_user,
            )

            # if this is a test contact, add an entry to our action log
            if run.contact.is_test:  # pragma: no cover
                log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % (
                    reverse("api.log_read", args=[webhook_event.pk]),
                    status_code,
                )
                ActionLog.create(run, log_txt, safe=True)

        return result