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()
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()
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()
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"))
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"))
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()
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"))
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()
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()
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"))
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
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
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)
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
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
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
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)