예제 #1
0
    def start_call(self, call, to, from_, status_callback):

        channel = call.channel
        Contact.get_or_create(channel.org,
                              channel.created_by,
                              urns=[(TEL_SCHEME, to)])

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = unicode(Flow.handle_call(call, {}))

        # our config should have our http basic auth parameters and verboice channel
        config = channel.config_json()

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint,
                         urlencode(dict(channel=config['channel'],
                                        address=to)))
        response = requests.post(url,
                                 data=payload,
                                 auth=(config['username'],
                                       config['password'])).json()

        # store the verboice call id in our IVRCall
        call.external_id = response['call_id']
        call.status = IN_PROGRESS
        call.save()
예제 #2
0
파일: clients.py 프로젝트: songea/rapidpro
    def start_call(self, call, to, from_, status_callback):
        if not settings.SEND_CALLS:
            raise IVRException("SEND_CALLS set to False, skipping call start")

        channel = call.channel
        Contact.get_or_create(channel.org,
                              channel.created_by,
                              urns=[URN.from_tel(to)])

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = six.text_type(Flow.handle_call(call))

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint,
                         urlencode(
                             dict(channel=self.verboice_channel, address=to)))
        response = requests.post(url, data=payload, auth=self.auth).json()

        if 'call_id' not in response:
            raise IVRException(_('Verboice connection failed.'))

        # store the verboice call id in our IVRCall
        call.external_id = response['call_id']
        call.status = IVRCall.IN_PROGRESS
        call.save()
예제 #3
0
    def start_call(self, call, to, from_, status_callback):
        if not settings.SEND_CALLS:
            raise ValueError("SEND_CALLS set to False, skipping call start")

        channel = call.channel
        Contact.get_or_create(channel.org, URN.from_tel(to), channel)

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = str(Flow.handle_call(call))

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint, urlencode(dict(channel=self.verboice_channel, address=to)))
        response = requests.post(url, data=payload, auth=self.auth).json()

        if "call_id" not in response:
            call.status = IVRCall.FAILED
            call.save()

            raise IVRException(_("Verboice connection failed."))

        # store the verboice call id in our IVRCall
        call.external_id = response["call_id"]

        # the call was successfully sent to the IVR provider
        call.status = IVRCall.WIRED
        call.save()
예제 #4
0
    def post(self, request, *args, **kwargs):
        call = IVRCall.objects.filter(pk=kwargs['pk']).first()

        if not call:
            return HttpResponse("Not found", status=404)

        client = call.channel.get_ivr_client()
        if request.REQUEST.get('hangup', 0):
            if not request.user.is_anonymous():
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.calls.hangup(call.external_id)
                    return HttpResponse(json.dumps(dict(status='Canceled')),
                                        content_type="application/json")
                else:
                    return HttpResponse("Not found", status=404)

        if client.validate(request):
            status = request.POST.get('CallStatus', None)
            duration = request.POST.get('CallDuration', None)
            call.update_status(status, duration)

            # update any calls we have spawned with the same
            for child in call.child_calls.all():
                child.update_status(status, duration)
                child.save()

            call.save()

            # figure out if this is a callback due to an empty gather
            is_empty = '1' == request.GET.get('empty', '0')
            user_response = request.POST.copy()

            # if the user pressed pound, then record no digits as the input
            if is_empty:
                user_response['Digits'] = ''

            hangup = 'hangup' == user_response.get('Digits', None)

            if call.status in [IN_PROGRESS, RINGING] or hangup:
                if call.is_flow():
                    response = Flow.handle_call(call,
                                                user_response,
                                                hangup=hangup)
                    return HttpResponse(unicode(response))
            else:
                if call.status == COMPLETED:
                    # if our call is completed, hangup
                    run = FlowRun.objects.filter(call=call).first()
                    if run:
                        run.set_completed()
                return build_json_response(dict(message="Updated call status"))

        else:  # pragma: no cover
            # raise an exception that things weren't properly signed
            raise ValidationError("Invalid request signature")

        return build_json_response(dict(message="Unhandled"))
예제 #5
0
    def post(self, request, *args, **kwargs):
        call = IVRCall.objects.filter(pk=kwargs['pk']).first()

        if not call:
            return HttpResponse("Not found", status=404)

        client = call.channel.get_ivr_client()
        if request.REQUEST.get('hangup', 0):
            if not request.user.is_anonymous():
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.calls.hangup(call.external_id)
                    return HttpResponse(json.dumps(dict(status='Canceled')), content_type="application/json")
                else:
                    return HttpResponse("Not found", status=404)

        if client.validate(request):
            status = request.POST.get('CallStatus', None)
            duration = request.POST.get('CallDuration', None)
            call.update_status(status, duration)

            # update any calls we have spawned with the same
            for child in call.child_calls.all():
                child.update_status(status, duration)
                child.save()

            call.save()

            # figure out if this is a callback due to an empty gather
            is_empty = '1' == request.GET.get('empty', '0')
            user_response = request.POST.copy()

            # if the user pressed pound, then record no digits as the input
            if is_empty:
                user_response['Digits'] = ''

            hangup = 'hangup' == user_response.get('Digits', None)

            if call.status == IN_PROGRESS or hangup:
                if call.is_flow():
                    response = Flow.handle_call(call, user_response, hangup=hangup)
                    return HttpResponse(unicode(response))
            else:

                if call.status == COMPLETED:
                    # if our call is completed, hangup
                    run = FlowRun.objects.filter(call=call).first()
                    if run:
                        run.set_completed()
                return build_json_response(dict(message="Updated call status"))

        else:  # pragma: no cover
            # raise an exception that things weren't properly signed
            raise ValidationError("Invalid request signature")

        return build_json_response(dict(message="Unhandled"))
예제 #6
0
    def start_call(self, call, to, from_, status_callback):

        channel = call.channel
        Contact.get_or_create(channel.org, channel.created_by, urns=[(TEL_SCHEME, to)])

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = unicode(Flow.handle_call(call, {}))

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint, urlencode(dict(channel=self.verboice_channel, address=to)))
        response = requests.post(url, data=payload, auth=self.auth).json()

        # store the verboice call id in our IVRCall
        call.external_id = response['call_id']
        call.status = IN_PROGRESS
        call.save()
예제 #7
0
    def post(self, request, *args, **kwargs):
        from twilio.util import RequestValidator

        call = IVRCall.objects.filter(pk=kwargs['pk']).first()

        if not call:
            return HttpResponse("Not found", status=404)

        client = call.channel.get_ivr_client()
        if request.REQUEST.get('hangup', 0):
            if not request.user.is_anonymous():
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.calls.hangup(call.external_id)
                    return HttpResponse(json.dumps(dict(status='Canceled')),
                                        content_type="application/json")
                else:
                    return HttpResponse("Not found", status=404)

        if client.validate(request):
            call.update_status(request.POST.get('CallStatus', None),
                               request.POST.get('CallDuration', None))
            call.save()

            hangup = 'hangup' == request.POST.get('Digits', None)

            if call.status == IN_PROGRESS or hangup:
                if call.is_flow():
                    response = Flow.handle_call(call,
                                                request.POST,
                                                hangup=hangup)
                    return HttpResponse(unicode(response))
            else:

                if call.status == COMPLETED:
                    # if our call is completed, hangup
                    run = FlowRun.objects.filter(call=call).first()
                    if run:
                        run.set_completed()
                        run.expire()
                return build_json_response(dict(message="Updated call status"))

        else:  # pragma: no cover
            # raise an exception that things weren't properly signed
            raise ValidationError("Invalid request signature")

        return build_json_response(dict(message="Unhandled"))
예제 #8
0
파일: views.py 프로젝트: thierhost/rapidpro
    def post(self, request, *args, **kwargs):
        from twilio.util import RequestValidator

        call = IVRCall.objects.filter(pk=kwargs['pk']).first()

        if not call:
            return HttpResponse("Not found", status=404)

        client = call.channel.get_ivr_client()
        if request.REQUEST.get('hangup', 0):
            if not request.user.is_anonymous():
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.calls.hangup(call.external_id)
                    return HttpResponse(json.dumps(dict(status='Canceled')), content_type="application/json")
                else:
                    return HttpResponse("Not found", status=404)

        if client.validate(request):
            call.update_status(request.POST.get('CallStatus', None),
                               request.POST.get('CallDuration', None))
            call.save()

            hangup = 'hangup' == request.POST.get('Digits', None)

            if call.status == IN_PROGRESS or hangup:
                if call.is_flow():
                    response = Flow.handle_call(call, request.POST, hangup=hangup)
                    return HttpResponse(unicode(response))
            else:

                if call.status == COMPLETED:
                    # if our call is completed, hangup
                    run = FlowRun.objects.filter(call=call).first()
                    if run:
                        run.set_completed()
                        run.expire()
                return build_json_response(dict(message="Updated call status"))

        else:  # pragma: no cover
            # raise an exception that things weren't properly signed
            raise ValidationError("Invalid request signature")

        return build_json_response(dict(message="Unhandled"))
예제 #9
0
    def start_call(self, call, to, from_, status_callback):

        channel = call.channel
        Contact.get_or_create(channel.org, channel.created_by, urns=[(TEL_SCHEME, to)])

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = unicode(Flow.handle_call(call, {}))

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint, urlencode(dict(channel=self.verboice_channel, address=to)))
        response = requests.post(url, data=payload, auth=self.auth).json()

        if 'call_id' not in response:
            raise IVRException(_('Verboice connection failed.'))

        # store the verboice call id in our IVRCall
        call.external_id = response['call_id']
        call.status = IN_PROGRESS
        call.save()
예제 #10
0
    def start_call(self, call, to, from_, status_callback):

        channel = call.channel
        contact = Contact.get_or_create(channel.created_by, channel.org, urns=[(TEL_SCHEME, to)])

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = unicode(Flow.handle_call(call, {}))

        # our config should have our http basic auth parameters and verboice channel
        config = channel.config_json()

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint, urlencode(dict(channel=config['channel'], address=to)))
        response = requests.post(url, data=payload, auth=(config['username'], config['password'])).json()

        # store the verboice call id in our IVRCall
        call.external_id = response['call_id']
        call.status = IN_PROGRESS
        call.save()
예제 #11
0
    def post(self, request, *args, **kwargs):
        from twilio.util import RequestValidator

        call = IVRCall.objects.filter(pk=kwargs['pk']).first()

        if not call:
            return HttpResponse("Not found", status=404)

        client = call.channel.get_ivr_client()
        if request.REQUEST.get('hangup', 0):
            if not request.user.is_anonymous():
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.calls.hangup(call.external_id)
                    return HttpResponse(json.dumps(dict(status='Canceled')), content_type="application/json")
                else:
                    return HttpResponse("Not found", status=404)

        validator = RequestValidator(client.auth[1])
        signature = request.META.get('HTTP_X_TWILIO_SIGNATURE', '')

        base_url = settings.TEMBA_HOST
        url = "https://%s%s" % (base_url, request.get_full_path())

        # make sure this is coming from twilio
        if validator.validate(url, request.POST, signature):
            call.update_status(request.POST.get('CallStatus', None),
                               request.POST.get('CallDuration', None))
            call.save()

            if call.status == IN_PROGRESS:
                if call.is_flow():
                    response = Flow.handle_call(call, request.POST)
                    return HttpResponse(unicode(response))
            else:
                return build_json_response(dict(message="Updated call status"))

        else:  # pragma: no cover
            # raise an exception that things weren't properly signed
            raise ValidationError("Invalid request signature")

        return build_json_response(dict(message="Unhandled"))
예제 #12
0
    def post(self, request, *args, **kwargs):
        call = IVRCall.objects.filter(pk=kwargs['pk']).first()

        if not call:
            return HttpResponse("Not found", status=404)

        channel = call.channel
        channel_type = channel.channel_type
        client = channel.get_ivr_client()

        request_body = request.body
        request_method = request.method
        request_path = request.get_full_path()

        if channel_type in Channel.TWIML_CHANNELS and request.POST.get(
                'hangup', 0):
            if not request.user.is_anonymous():
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.calls.hangup(call.external_id)
                    return HttpResponse(json.dumps(dict(status='Canceled')),
                                        content_type="application/json")
                else:  # pragma: no cover
                    return HttpResponse("Not found", status=404)

        input_redirect = '1' == request.GET.get('input_redirect', '0')
        if client.validate(request):
            status = None
            duration = None
            if channel_type in Channel.TWIML_CHANNELS:
                status = request.POST.get('CallStatus', None)
                duration = request.POST.get('CallDuration', None)
            elif channel_type in Channel.NCCO_CHANNELS:
                if request_body:
                    body_json = json.loads(request_body)
                    status = body_json.get('status', None)
                    duration = body_json.get('duration', None)

                # force in progress call status for fake (input) redirects
                if input_redirect:
                    status = 'answered'

            call.update_status(
                status, duration,
                channel_type)  # update any calls we have spawned with the same
            call.save()

            resume = request.GET.get('resume', 0)
            user_response = request.POST.copy()

            hangup = False
            saved_media_url = None
            text = None
            media_url = None

            if channel_type in Channel.TWIML_CHANNELS:

                # figure out if this is a callback due to an empty gather
                is_empty = '1' == request.GET.get('empty', '0')

                # if the user pressed pound, then record no digits as the input
                if is_empty:
                    user_response['Digits'] = ''

                hangup = 'hangup' == user_response.get('Digits', None)

                media_url = user_response.get('RecordingUrl', None)
                # if we've been sent a recording, go grab it
                if media_url:
                    saved_media_url = client.download_media(media_url)

                # parse the user response
                text = user_response.get('Digits', None)

            elif channel_type in Channel.NCCO_CHANNELS:
                if request_body:
                    body_json = json.loads(request_body)
                    media_url = body_json.get('recording_url', None)

                    if media_url:
                        cache.set('last_call:media_url:%d' % call.pk,
                                  media_url, None)

                    media_url = cache.get('last_call:media_url:%d' % call.pk,
                                          None)
                    text = body_json.get('dtmf', None)
                    if input_redirect:
                        text = None

                has_event = '1' == request.GET.get('has_event', '0')
                if has_event:
                    return HttpResponse(six.text_type(''))

                save_media = '1' == request.GET.get('save_media', '0')
                if media_url:
                    if save_media:
                        saved_media_url = client.download_media(media_url)
                        cache.delete('last_call:media_url:%d' % call.pk)
                    else:
                        response_msg = 'media URL saved'
                        ChannelLog.log_ivr_interaction(
                            call, "Saved media URL", request_body,
                            six.text_type(response_msg), request_path,
                            request_method)
                        return HttpResponse(six.text_type(response_msg))

            if call.status not in IVRCall.DONE or hangup:
                if call.is_ivr():
                    response = Flow.handle_call(
                        call,
                        text=text,
                        saved_media_url=saved_media_url,
                        hangup=hangup,
                        resume=resume)
                    if channel_type in Channel.NCCO_CHANNELS:

                        ChannelLog.log_ivr_interaction(call,
                                                       "Returned response",
                                                       request_body,
                                                       six.text_type(response),
                                                       request_path,
                                                       request_method)
                        return JsonResponse(json.loads(
                            six.text_type(response)),
                                            safe=False)

                    ChannelLog.log_ivr_interaction(call, "Returned response",
                                                   request_body,
                                                   six.text_type(response),
                                                   request_path,
                                                   request_method)
                    return HttpResponse(six.text_type(response))
            else:
                if call.status == IVRCall.COMPLETED:
                    # if our call is completed, hangup
                    run = FlowRun.objects.filter(session=call).first()
                    if run:
                        run.set_completed()

                response = dict(message="Updated call status",
                                call=dict(status=call.get_status_display(),
                                          duration=call.duration))
                ChannelLog.log_ivr_interaction(
                    call,
                    "Updated call status: %s" % call.get_status_display(),
                    request_body, json.dumps(response), request_path,
                    request_method)
                return JsonResponse(response)

        else:  # pragma: no cover

            error_message = "Invalid request signature"
            ChannelLog.log_ivr_interaction(call,
                                           error_message,
                                           request_body,
                                           error_message,
                                           request_path,
                                           request_method,
                                           is_error=True)
            # raise an exception that things weren't properly signed
            raise ValidationError(error_message)

        return JsonResponse(dict(message="Unhandled"))  # pragma: no cover
예제 #13
0
    def post(self, request, *args, **kwargs):
        from twilio.request_validator import RequestValidator
        from temba.flows.models import FlowSession

        signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")
        url = "https://" + request.get_host() + "%s" % request.get_full_path()

        channel_uuid = kwargs.get("uuid")
        call_sid = self.get_param("CallSid")
        direction = self.get_param("Direction")
        status = self.get_param("CallStatus")
        to_number = self.get_param("To")
        to_country = self.get_param("ToCountry")
        from_number = self.get_param("From")

        # Twilio sometimes sends un-normalized numbers
        if to_number and not to_number.startswith("+") and to_country:  # pragma: no cover
            to_number, valid = URN.normalize_number(to_number, to_country)

        # see if it's a twilio call being initiated
        if to_number and call_sid and direction == "inbound" and status == "ringing":

            # find a channel that knows how to answer twilio calls
            channel = self.get_ringing_channel(uuid=channel_uuid)
            if not channel:
                response = VoiceResponse()
                response.say("Sorry, there is no channel configured to take this call. Goodbye.")
                response.hangup()
                return HttpResponse(str(response))

            org = channel.org

            if self.get_channel_type() == "T" and not org.is_connected_to_twilio():
                return HttpResponse("No Twilio account is connected", status=400)

            client = self.get_client(channel=channel)
            validator = RequestValidator(client.auth[1])
            signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")

            url = "https://%s%s" % (request.get_host(), request.get_full_path())

            if validator.validate(url, request.POST, signature):
                from temba.ivr.models import IVRCall

                # find a contact for the one initiating us
                urn = URN.from_tel(from_number)
                contact, urn_obj = Contact.get_or_create(channel.org, urn, channel)

                flow = Trigger.find_flow_for_inbound_call(contact)

                if flow:
                    call = IVRCall.create_incoming(channel, contact, urn_obj, channel.created_by, call_sid)
                    session = FlowSession.create(contact, connection=call)

                    call.update_status(
                        request.POST.get("CallStatus", None), request.POST.get("CallDuration", None), "T"
                    )
                    call.save()

                    FlowRun.create(flow, contact, session=session, connection=call)
                    response = Flow.handle_call(call)
                    return HttpResponse(str(response))

                else:

                    # we don't have an inbound trigger to deal with this call.
                    response = channel.generate_ivr_response()

                    # say nothing and hangup, this is a little rude, but if we reject the call, then
                    # they'll get a non-working number error. We send 'busy' when our server is down
                    # so we don't want to use that here either.
                    response.say("")
                    response.hangup()

                    # if they have a missed call trigger, fire that off
                    Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL, channel)

                    # either way, we need to hangup now
                    return HttpResponse(str(response))

        # check for call progress events, these include post-call hangup notifications
        if request.POST.get("CallbackSource", None) == "call-progress-events":
            if call_sid:
                from temba.ivr.models import IVRCall

                call = IVRCall.objects.filter(external_id=call_sid).first()
                if call:
                    call.update_status(
                        request.POST.get("CallStatus", None), request.POST.get("CallDuration", None), "TW"
                    )
                    call.save()
                    return HttpResponse("Call status updated")
            return HttpResponse("No call found")

        return HttpResponse("Not Handled, unknown action", status=400)  # pragma: no cover
예제 #14
0
    def get(self, request, *args, **kwargs):
        from temba.flows.models import FlowSession
        from temba.ivr.models import IVRCall

        action = kwargs["action"].lower()

        request_body = force_text(request.body)
        request_path = request.get_full_path()
        request_method = request.method

        request_uuid = kwargs["uuid"]

        if action == "event":
            if not request_body:
                return HttpResponse("")

            body_json = json.loads(request_body)
            status = body_json.get("status", None)
            duration = body_json.get("duration", None)
            call_uuid = body_json.get("uuid", None)
            conversation_uuid = body_json.get("conversation_uuid", None)

            if call_uuid is None:
                return HttpResponse("Missing uuid parameter, ignoring")

            call = IVRCall.objects.filter(external_id=call_uuid).first()
            if not call:
                # try looking up by the conversation uuid (inbound calls start with that)
                call = IVRCall.objects.filter(external_id=conversation_uuid).first()
                if call:
                    call.external_id = call_uuid
                    call.save()
                else:
                    response = dict(message="Call not found for %s" % call_uuid)
                    return JsonResponse(response)

            channel = call.channel
            channel_type = channel.channel_type
            call.update_status(status, duration, channel_type)
            call.save()

            response = dict(
                description="Updated call status", call=dict(status=call.get_status_display(), duration=call.duration)
            )

            event = HttpEvent(request_method, request_path, request_body, 200, json.dumps(response))
            ChannelLog.log_ivr_interaction(call, "Updated call status", event)

            if call.status == IVRCall.COMPLETED:
                # if our call is completed, hangup
                runs = FlowRun.objects.filter(connection=call)
                for run in runs:
                    if not run.is_completed():
                        run.set_completed(exit_uuid=None)

            return JsonResponse(response)

        if action == "answer":
            if not request_body:
                return HttpResponse("")

            body_json = json.loads(request_body)
            from_number = body_json.get("from", None)
            channel_number = body_json.get("to", None)
            external_id = body_json.get("conversation_uuid", None)

            if not from_number or not channel_number or not external_id:
                return HttpResponse("Missing parameters, Ignoring")

            # look up the channel
            address_q = Q(address=channel_number) | Q(address=("+" + channel_number))
            channel = Channel.objects.filter(address_q).filter(is_active=True, channel_type="NX").first()

            # make sure we got one, and that it matches the key for our org
            org_uuid = None
            if channel:
                org_uuid = channel.org.config.get(NEXMO_UUID, None)

            if not channel or org_uuid != request_uuid:
                return HttpResponse("Channel not found for number: %s" % channel_number, status=404)

            urn = URN.from_tel(from_number)
            contact, urn_obj = Contact.get_or_create(channel.org, urn, channel)

            flow = Trigger.find_flow_for_inbound_call(contact)

            if flow:
                call = IVRCall.create_incoming(channel, contact, urn_obj, channel.created_by, external_id)
                session = FlowSession.create(contact, connection=call)

                FlowRun.create(flow, contact, session=session, connection=call)
                response = Flow.handle_call(call)
                channel_type = channel.channel_type
                call.update_status("answered", None, channel_type)

                event = HttpEvent(request_method, request_path, request_body, 200, str(response))
                ChannelLog.log_ivr_interaction(call, "Incoming request for call", event)
                return JsonResponse(json.loads(str(response)), safe=False)
            else:
                # we don't have an inbound trigger to deal with this call.
                response = channel.generate_ivr_response()

                # say nothing and hangup, this is a little rude, but if we reject the call, then
                # they'll get a non-working number error. We send 'busy' when our server is down
                # so we don't want to use that here either.
                response.say("")
                response.hangup()

                # if they have a missed call trigger, fire that off
                Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL, channel)

                # either way, we need to hangup now
                return JsonResponse(json.loads(str(response)), safe=False)
예제 #15
0
파일: views.py 프로젝트: mxabierto/rapidpro
    def post(self, request, *args, **kwargs):
        call = IVRCall.objects.filter(pk=kwargs["pk"]).first()

        if not call:
            return HttpResponse("Not found", status=404)

        channel = call.channel

        if not (channel.is_active and channel.org):
            return HttpResponse("No channel found", status=400)

        channel_type = channel.channel_type
        ivr_protocol = Channel.get_type_from_code(channel_type).ivr_protocol
        client = channel.get_ivr_client()

        request_body = force_text(request.body)
        request_method = request.method
        request_path = request.get_full_path()

        if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML and request.POST.get("hangup", 0):
            if not request.user.is_anonymous:
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.hangup(call)
                    return HttpResponse(json.dumps(dict(status="Canceled")), content_type="application/json")
                else:  # pragma: no cover
                    return HttpResponse("Not found", status=404)

        input_redirect = "1" == request.GET.get("input_redirect", "0")
        if client.validate(request):
            status = None
            duration = None
            if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML:
                status = request.POST.get("CallStatus", None)
                duration = request.POST.get("CallDuration", None)
            elif ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO:
                if request_body:
                    body_json = json.loads(request_body)
                    status = body_json.get("status", None)
                    duration = body_json.get("duration", None)

                # force in progress call status for fake (input) redirects
                if input_redirect:
                    status = "answered"

            # nexmo does not set status for some callbacks
            if status is not None:
                call.update_status(status, duration, channel_type)  # update any calls we have spawned with the same
                call.save()

            resume = request.GET.get("resume", 0)
            user_response = request.POST.copy()

            hangup = False
            saved_media_url = None
            text = None
            media_url = None
            has_event = False

            if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML:

                # figure out if this is a callback due to an empty gather
                is_empty = "1" == request.GET.get("empty", "0")

                # if the user pressed pound, then record no digits as the input
                if is_empty:
                    user_response["Digits"] = ""

                hangup = "hangup" == user_response.get("Digits", None)

                media_url = user_response.get("RecordingUrl", None)
                # if we've been sent a recording, go grab it
                if media_url:
                    saved_media_url = client.download_media(media_url)

                # parse the user response
                text = user_response.get("Digits", None)

            elif ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO:
                if request_body:
                    body_json = json.loads(request_body)
                    media_url = body_json.get("recording_url", None)

                    if media_url:
                        cache.set("last_call:media_url:%d" % call.pk, media_url, None)

                    media_url = cache.get("last_call:media_url:%d" % call.pk, None)
                    text = body_json.get("dtmf", None)
                    if input_redirect:
                        text = None

                has_event = "1" == request.GET.get("has_event", "0")
                save_media = "1" == request.GET.get("save_media", "0")
                if media_url:
                    if save_media:
                        saved_media_url = client.download_media(call, media_url)
                        cache.delete("last_call:media_url:%d" % call.pk)
                    else:
                        response_msg = "Saved media url"
                        response = dict(message=response_msg)

                        event = HttpEvent(request_method, request_path, request_body, 200, json.dumps(response))
                        ChannelLog.log_ivr_interaction(call, response_msg, event)
                        return JsonResponse(response)

            if not has_event and call.status not in IVRCall.DONE or hangup:
                if call.is_ivr():
                    response = Flow.handle_call(
                        call, text=text, saved_media_url=saved_media_url, hangup=hangup, resume=resume
                    )
                    event = HttpEvent(request_method, request_path, request_body, 200, str(response))
                    if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO:
                        ChannelLog.log_ivr_interaction(call, "Incoming request for call", event)

                        # TODO: what's special here that this needs to be different?
                        return JsonResponse(json.loads(str(response)), safe=False)

                    ChannelLog.log_ivr_interaction(call, "Incoming request for call", event)
                    return HttpResponse(str(response), content_type="text/xml; charset=utf-8")
            else:

                if call.status == IVRCall.COMPLETED:
                    # if our call is completed, hangup
                    runs = FlowRun.objects.filter(connection=call)
                    for run in runs:
                        if not run.is_completed():
                            run.set_completed(exit_uuid=None)

                response = dict(
                    description="Updated call status",
                    call=dict(status=call.get_status_display(), duration=call.duration),
                )

                event = HttpEvent(request_method, request_path, request_body, 200, json.dumps(response))
                ChannelLog.log_ivr_interaction(call, "Updated call status", event)
                return JsonResponse(response)

        else:  # pragma: no cover

            error = "Invalid request signature"
            event = HttpEvent(request_method, request_path, request_body, 200, error)
            ChannelLog.log_ivr_interaction(call, error, event, is_error=True)
            # raise an exception that things weren't properly signed
            raise ValidationError(error)

        return JsonResponse(dict(message="Unhandled"))  # pragma: no cover
예제 #16
0
    def post(self, request, *args, **kwargs):
        call = IVRCall.objects.filter(pk=kwargs["pk"]).first()

        if not call:
            return HttpResponse("Not found", status=404)

        channel = call.channel

        if not (channel.is_active and channel.org):
            return HttpResponse("No channel found", status=400)

        channel_type = channel.channel_type
        ivr_protocol = Channel.get_type_from_code(channel_type).ivr_protocol
        client = channel.get_ivr_client()

        request_body = force_text(request.body)
        request_method = request.method
        request_path = request.get_full_path()

        if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML and request.POST.get(
                "hangup", 0):
            if not request.user.is_anonymous:
                user_org = request.user.get_org()
                if user_org and user_org.pk == call.org.pk:
                    client.hangup(call)
                    return HttpResponse(json.dumps(dict(status="Canceled")),
                                        content_type="application/json")
                else:  # pragma: no cover
                    return HttpResponse("Not found", status=404)

        input_redirect = "1" == request.GET.get("input_redirect", "0")
        if client.validate(request):
            status = None
            duration = None
            if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML:
                status = request.POST.get("CallStatus", None)
                duration = request.POST.get("CallDuration", None)
            elif ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO:
                if request_body:
                    body_json = json.loads(request_body)
                    status = body_json.get("status", None)
                    duration = body_json.get("duration", None)

                # force in progress call status for fake (input) redirects
                if input_redirect:
                    status = "answered"

            # nexmo does not set status for some callbacks
            if status is not None:
                call.update_status(
                    status, duration, channel_type
                )  # update any calls we have spawned with the same
                call.save()

            resume = request.GET.get("resume", 0)
            user_response = request.POST.copy()

            hangup = False
            saved_media_url = None
            text = None
            media_url = None
            has_event = False

            if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML:

                # figure out if this is a callback due to an empty gather
                is_empty = "1" == request.GET.get("empty", "0")

                # if the user pressed pound, then record no digits as the input
                if is_empty:
                    user_response["Digits"] = ""

                hangup = "hangup" == user_response.get("Digits", None)

                media_url = user_response.get("RecordingUrl", None)
                # if we've been sent a recording, go grab it
                if media_url:
                    saved_media_url = client.download_media(media_url)

                # parse the user response
                text = user_response.get("Digits", None)

            elif ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO:
                if request_body:
                    body_json = json.loads(request_body)
                    media_url = body_json.get("recording_url", None)

                    if media_url:
                        cache.set("last_call:media_url:%d" % call.pk,
                                  media_url, None)

                    media_url = cache.get("last_call:media_url:%d" % call.pk,
                                          None)
                    text = body_json.get("dtmf", None)
                    if input_redirect:
                        text = None

                has_event = "1" == request.GET.get("has_event", "0")
                save_media = "1" == request.GET.get("save_media", "0")
                if media_url:
                    if save_media:
                        saved_media_url = client.download_media(
                            call, media_url)
                        cache.delete("last_call:media_url:%d" % call.pk)
                    else:
                        response_msg = "Saved media url"
                        response = dict(message=response_msg)

                        event = HttpEvent(request_method, request_path,
                                          request_body, 200,
                                          json.dumps(response))
                        ChannelLog.log_ivr_interaction(call, response_msg,
                                                       event)
                        return JsonResponse(response)

            if not has_event and call.status not in IVRCall.DONE or hangup:
                if call.is_ivr():
                    response = Flow.handle_call(
                        call,
                        text=text,
                        saved_media_url=saved_media_url,
                        hangup=hangup,
                        resume=resume)
                    event = HttpEvent(request_method, request_path,
                                      request_body, 200, str(response))
                    if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO:
                        ChannelLog.log_ivr_interaction(
                            call, "Incoming request for call", event)

                        # TODO: what's special here that this needs to be different?
                        return JsonResponse(json.loads(str(response)),
                                            safe=False)

                    ChannelLog.log_ivr_interaction(
                        call, "Incoming request for call", event)
                    return HttpResponse(str(response),
                                        content_type="text/xml; charset=utf-8")
            else:

                if call.status == IVRCall.COMPLETED:
                    # if our call is completed, hangup
                    runs = FlowRun.objects.filter(connection=call)
                    for run in runs:
                        if not run.is_completed():
                            run.set_completed(exit_uuid=None)

                response = dict(
                    description="Updated call status",
                    call=dict(status=call.get_status_display(),
                              duration=call.duration),
                )

                event = HttpEvent(request_method, request_path, request_body,
                                  200, json.dumps(response))
                ChannelLog.log_ivr_interaction(call, "Updated call status",
                                               event)
                return JsonResponse(response)

        else:  # pragma: no cover

            error = "Invalid request signature"
            event = HttpEvent(request_method, request_path, request_body, 200,
                              error)
            ChannelLog.log_ivr_interaction(call, error, event, is_error=True)
            # raise an exception that things weren't properly signed
            raise ValidationError(error)

        return JsonResponse(dict(message="Unhandled"))  # pragma: no cover
예제 #17
0
    def post(self, request, *args, **kwargs):
        from twilio.request_validator import RequestValidator
        from temba.flows.models import FlowSession

        signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")
        url = "https://" + request.get_host() + "%s" % request.get_full_path()

        channel_uuid = kwargs.get("uuid")
        call_sid = self.get_param("CallSid")
        direction = self.get_param("Direction")
        status = self.get_param("CallStatus")
        to_number = self.get_param("To")
        to_country = self.get_param("ToCountry")
        from_number = self.get_param("From")

        # Twilio sometimes sends un-normalized numbers
        if to_number and not to_number.startswith(
                "+") and to_country:  # pragma: no cover
            to_number, valid = URN.normalize_number(to_number, to_country)

        # see if it's a twilio call being initiated
        if to_number and call_sid and direction == "inbound" and status == "ringing":

            # find a channel that knows how to answer twilio calls
            channel = self.get_ringing_channel(uuid=channel_uuid)
            if not channel:
                response = VoiceResponse()
                response.say(
                    "Sorry, there is no channel configured to take this call. Goodbye."
                )
                response.hangup()
                return HttpResponse(str(response))

            org = channel.org

            if self.get_channel_type(
            ) == "T" and not org.is_connected_to_twilio():
                return HttpResponse("No Twilio account is connected",
                                    status=400)

            client = self.get_client(channel=channel)
            validator = RequestValidator(client.auth[1])
            signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")

            url = "https://%s%s" % (request.get_host(),
                                    request.get_full_path())

            if validator.validate(url, request.POST, signature):
                from temba.ivr.models import IVRCall

                # find a contact for the one initiating us
                urn = URN.from_tel(from_number)
                contact, urn_obj = Contact.get_or_create(
                    channel.org, urn, channel)

                flow = Trigger.find_flow_for_inbound_call(contact)

                if flow:
                    call = IVRCall.create_incoming(channel, contact, urn_obj,
                                                   channel.created_by,
                                                   call_sid)
                    session = FlowSession.create(contact, connection=call)

                    call.update_status(request.POST.get("CallStatus", None),
                                       request.POST.get("CallDuration", None),
                                       "T")
                    call.save()

                    FlowRun.create(flow,
                                   contact,
                                   session=session,
                                   connection=call)
                    response = Flow.handle_call(call)
                    return HttpResponse(str(response))

                else:

                    # we don't have an inbound trigger to deal with this call.
                    response = channel.generate_ivr_response()

                    # say nothing and hangup, this is a little rude, but if we reject the call, then
                    # they'll get a non-working number error. We send 'busy' when our server is down
                    # so we don't want to use that here either.
                    response.say("")
                    response.hangup()

                    # if they have a missed call trigger, fire that off
                    Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL,
                                           channel)

                    # either way, we need to hangup now
                    return HttpResponse(str(response))

        # check for call progress events, these include post-call hangup notifications
        if request.POST.get("CallbackSource", None) == "call-progress-events":
            if call_sid:
                from temba.ivr.models import IVRCall

                call = IVRCall.objects.filter(external_id=call_sid).first()
                if call:
                    call.update_status(request.POST.get("CallStatus", None),
                                       request.POST.get("CallDuration", None),
                                       "TW")
                    call.save()
                    return HttpResponse("Call status updated")
            return HttpResponse("No call found")

        return HttpResponse("Not Handled, unknown action",
                            status=400)  # pragma: no cover
예제 #18
0
    def get(self, request, *args, **kwargs):
        from temba.flows.models import FlowSession
        from temba.ivr.models import IVRCall

        action = kwargs["action"].lower()

        request_body = force_text(request.body)
        request_path = request.get_full_path()
        request_method = request.method

        request_uuid = kwargs["uuid"]

        if action == "event":
            if not request_body:
                return HttpResponse("")

            body_json = json.loads(request_body)
            status = body_json.get("status", None)
            duration = body_json.get("duration", None)
            call_uuid = body_json.get("uuid", None)
            conversation_uuid = body_json.get("conversation_uuid", None)

            if call_uuid is None:
                return HttpResponse("Missing uuid parameter, ignoring")

            call = IVRCall.objects.filter(external_id=call_uuid).first()
            if not call:
                # try looking up by the conversation uuid (inbound calls start with that)
                call = IVRCall.objects.filter(
                    external_id=conversation_uuid).first()
                if call:
                    call.external_id = call_uuid
                    call.save()
                else:
                    response = dict(message="Call not found for %s" %
                                    call_uuid)
                    return JsonResponse(response)

            channel = call.channel
            channel_type = channel.channel_type
            call.update_status(status, duration, channel_type)
            call.save()

            response = dict(description="Updated call status",
                            call=dict(status=call.get_status_display(),
                                      duration=call.duration))

            event = HttpEvent(request_method, request_path, request_body, 200,
                              json.dumps(response))
            ChannelLog.log_ivr_interaction(call, "Updated call status", event)

            if call.status == IVRCall.COMPLETED:
                # if our call is completed, hangup
                runs = FlowRun.objects.filter(connection=call)
                for run in runs:
                    if not run.is_completed():
                        run.set_completed(exit_uuid=None)

            return JsonResponse(response)

        if action == "answer":
            if not request_body:
                return HttpResponse("")

            body_json = json.loads(request_body)
            from_number = body_json.get("from", None)
            channel_number = body_json.get("to", None)
            external_id = body_json.get("conversation_uuid", None)

            if not from_number or not channel_number or not external_id:
                return HttpResponse("Missing parameters, Ignoring")

            # look up the channel
            address_q = Q(address=channel_number) | Q(address=("+" +
                                                               channel_number))
            channel = Channel.objects.filter(address_q).filter(
                is_active=True, channel_type="NX").first()

            # make sure we got one, and that it matches the key for our org
            org_uuid = None
            if channel:
                org_uuid = channel.org.config.get(NEXMO_UUID, None)

            if not channel or org_uuid != request_uuid:
                return HttpResponse("Channel not found for number: %s" %
                                    channel_number,
                                    status=404)

            urn = URN.from_tel(from_number)
            contact, urn_obj = Contact.get_or_create(channel.org, urn, channel)

            flow = Trigger.find_flow_for_inbound_call(contact)

            if flow:
                call = IVRCall.create_incoming(channel, contact, urn_obj,
                                               channel.created_by, external_id)
                session = FlowSession.create(contact, connection=call)

                FlowRun.create(flow, contact, session=session, connection=call)
                response = Flow.handle_call(call)
                channel_type = channel.channel_type
                call.update_status("answered", None, channel_type)

                event = HttpEvent(request_method, request_path, request_body,
                                  200, str(response))
                ChannelLog.log_ivr_interaction(call,
                                               "Incoming request for call",
                                               event)
                return JsonResponse(json.loads(str(response)), safe=False)
            else:
                # we don't have an inbound trigger to deal with this call.
                response = channel.generate_ivr_response()

                # say nothing and hangup, this is a little rude, but if we reject the call, then
                # they'll get a non-working number error. We send 'busy' when our server is down
                # so we don't want to use that here either.
                response.say("")
                response.hangup()

                # if they have a missed call trigger, fire that off
                Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL,
                                       channel)

                # either way, we need to hangup now
                return JsonResponse(json.loads(str(response)), safe=False)