def update_status(self, status, duration): """ Updates our status from a twilio status string """ if status == 'queued': self.status = QUEUED elif status == 'ringing': self.status = RINGING elif status == 'no-answer': self.status = NO_ANSWER elif status == 'in-progress': if self.status != IN_PROGRESS: self.started_on = timezone.now() self.status = IN_PROGRESS elif status == 'completed': if self.contact.is_test: run = FlowRun.objects.filter(call=self) if run: ActionLog.create(run[0], _("Call ended.")) self.status = COMPLETED elif status == 'busy': self.status = BUSY elif status == 'failed': self.status = FAILED elif status == 'canceled': self.status = CANCELED self.duration = duration
def update_status(self, status, duration, channel_type): """ Updates our status from a provide call status string """ from temba.flows.models import FlowRun, ActionLog previous_status = self.status ivr_protocol = Channel.get_type_from_code(channel_type).ivr_protocol if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML: if status == 'queued': self.status = self.QUEUED elif status == 'ringing': self.status = self.RINGING elif status == 'no-answer': self.status = self.NO_ANSWER elif status == 'in-progress': if self.status != self.IN_PROGRESS: self.started_on = timezone.now() self.status = self.IN_PROGRESS elif status == 'completed': if self.contact.is_test: run = FlowRun.objects.filter(connection=self) if run: ActionLog.create(run[0], _("Call ended.")) self.status = self.COMPLETED elif status == 'busy': self.status = self.BUSY elif status == 'failed': self.status = self.FAILED elif status == 'canceled': self.status = self.CANCELED elif ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO: if status in ('ringing', 'started'): self.status = self.RINGING elif status == 'answered': self.status = self.IN_PROGRESS elif status == 'completed': self.status = self.COMPLETED elif status == 'failed': self.status = self.FAILED elif status in ('rejected', 'busy'): self.status = self.BUSY elif status in ('unanswered', 'timeout'): self.status = self.NO_ANSWER # if we are done, mark our ended time if self.status in ChannelSession.DONE: self.ended_on = timezone.now() if duration is not None: self.duration = duration # if we are moving into IN_PROGRESS, make sure our runs have proper expirations if previous_status in [self.QUEUED, self.PENDING] and self.status in [self.IN_PROGRESS, self.RINGING]: runs = FlowRun.objects.filter(connection=self, is_active=True, expires_on=None) for run in runs: run.update_expiration()
def do_start_call(self, qs=None): client = self.channel.get_ivr_client() if client: try: url = "https://%s%s" % (settings.TEMBA_HOST, reverse('ivr.ivrcall_handle', args=[self.pk])) if qs: url = "%s?%s" % (url, qs) tel = None # if we are working with a test contact, look for user settings if self.contact.is_test: user_settings = self.created_by.get_settings() if user_settings.tel: tel = user_settings.tel run = FlowRun.objects.filter(call=self) if run: ActionLog.create(run[0], "Placing test call to %s" % user_settings.get_tel_formatted()) if not tel: tel_urn = self.contact_urn tel = tel_urn.path client.start_call(self, to=tel, from_=self.channel.address, status_callback=url) except Exception: # pragma: no cover import traceback traceback.print_exc() self.status = FAILED self.save()
def update_status(self, status: str, duration: float, channel_type: str): """ Updates our status from a provide call status string """ if not status: raise ValueError( f"IVR Call status must be defined, got: '{status}'") previous_status = self.status from temba.flows.models import FlowRun, ActionLog ivr_protocol = Channel.get_type_from_code(channel_type).ivr_protocol if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML: self.status = self.derive_ivr_status_twiml(status, previous_status) elif ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO: self.status = self.derive_ivr_status_nexmo(status, previous_status) else: # pragma: no cover raise ValueError(f"Unhandled IVR protocol: {ivr_protocol}") # if we are in progress, mark our start time if self.status == self.IN_PROGRESS and previous_status != self.IN_PROGRESS: self.started_on = timezone.now() # if we are done, mark our ended time if self.status in ChannelSession.DONE: self.ended_on = timezone.now() if self.contact.is_test: run = FlowRun.objects.filter(connection=self) if run: ActionLog.create(run[0], _("Call ended.")) if self.status in ChannelSession.RETRY_CALL and previous_status not in ChannelSession.RETRY_CALL: flow = self.get_flow() backoff_minutes = flow.metadata.get("ivr_retry", IVRCall.RETRY_BACKOFF_MINUTES) self.schedule_call_retry(backoff_minutes) if duration is not None: self.duration = duration # if we are moving into IN_PROGRESS, make sure our runs have proper expirations if previous_status in (self.PENDING, self.QUEUED, self.WIRED) and self.status in ( self.IN_PROGRESS, self.RINGING, ): runs = FlowRun.objects.filter(connection=self, is_active=True, expires_on=None) for run in runs: run.update_expiration()
def update_status(self, status: str, duration: float, channel_type: str): """ Updates our status from a provide call status string """ if not status: raise ValueError(f"IVR Call status must be defined, got: '{status}'") previous_status = self.status from temba.flows.models import FlowRun, ActionLog ivr_protocol = Channel.get_type_from_code(channel_type).ivr_protocol if ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_TWIML: self.status = self.derive_ivr_status_twiml(status, previous_status) elif ivr_protocol == ChannelType.IVRProtocol.IVR_PROTOCOL_NCCO: self.status = self.derive_ivr_status_nexmo(status, previous_status) else: # pragma: no cover raise ValueError(f"Unhandled IVR protocol: {ivr_protocol}") # if we are in progress, mark our start time if self.status == self.IN_PROGRESS and previous_status != self.IN_PROGRESS: self.started_on = timezone.now() # if we are done, mark our ended time if self.status in ChannelSession.DONE: self.ended_on = timezone.now() if self.contact.is_test: run = FlowRun.objects.filter(connection=self) if run: ActionLog.create(run[0], _("Call ended.")) if self.status in ChannelSession.RETRY_CALL and previous_status not in ChannelSession.RETRY_CALL: flow = self.get_flow() backoff_minutes = flow.metadata.get("ivr_retry", IVRCall.RETRY_BACKOFF_MINUTES) self.schedule_call_retry(backoff_minutes) if duration is not None: self.duration = duration # if we are moving into IN_PROGRESS, make sure our runs have proper expirations if previous_status in (self.PENDING, self.QUEUED, self.WIRED) and self.status in ( self.IN_PROGRESS, self.RINGING, ): runs = FlowRun.objects.filter(connection=self, is_active=True, expires_on=None) for run in runs: run.update_expiration()
def update_status(self, status, duration, channel_type): """ Updates our status from a provide call status string """ from temba.flows.models import FlowRun, ActionLog if channel_type in Channel.TWIML_CHANNELS: if status == 'queued': self.status = self.QUEUED elif status == 'ringing': self.status = self.RINGING elif status == 'no-answer': self.status = self.NO_ANSWER elif status == 'in-progress': if self.status != self.IN_PROGRESS: self.started_on = timezone.now() self.status = self.IN_PROGRESS elif status == 'completed': if self.contact.is_test: run = FlowRun.objects.filter(session=self) if run: ActionLog.create(run[0], _("Call ended.")) self.status = self.COMPLETED elif status == 'busy': self.status = self.BUSY elif status == 'failed': self.status = self.FAILED elif status == 'canceled': self.status = self.CANCELED elif channel_type in Channel.NCCO_CHANNELS: if status in ('ringing', 'started'): self.status = self.RINGING elif status == 'answered': self.status = self.IN_PROGRESS elif status == 'completed': self.status = self.COMPLETED elif status == 'failed': self.status = self.FAILED elif status in ('rejected', 'busy'): self.status = self.BUSY elif status in ('unanswered', 'timeout'): self.status = self.NO_ANSWER # if we are done, mark our ended time if self.status in ChannelSession.DONE: self.ended_on = timezone.now() if duration is not None: self.duration = duration
def do_start_call(self, qs=None): client = self.channel.get_ivr_client() domain = self.channel.callback_domain from temba.ivr.clients import IVRException from temba.flows.models import ActionLog, FlowRun if client: try: url = "https://%s%s" % ( domain, reverse("ivr.ivrcall_handle", args=[self.pk])) if qs: # pragma: no cover url = "%s?%s" % (url, qs) tel = None # if we are working with a test contact, look for user settings if self.contact.is_test: user_settings = self.created_by.get_settings() if user_settings.tel: tel = user_settings.tel run = FlowRun.objects.filter(connection=self) if run: ActionLog.create( run[0], "Placing test call to %s" % user_settings.get_tel_formatted()) if not tel: tel_urn = self.contact_urn tel = tel_urn.path client.start_call(self, to=tel, from_=self.channel.address, status_callback=url) except IVRException as e: import traceback traceback.print_exc() if self.contact.is_test: run = FlowRun.objects.filter(connection=self) ActionLog.create(run[0], "Call ended. %s" % str(e)) except Exception as e: # pragma: no cover import traceback traceback.print_exc() self.status = self.FAILED self.save() if self.contact.is_test: run = FlowRun.objects.filter(connection=self) ActionLog.create(run[0], "Call ended.")
def do_start_call(self, qs=None): client = self.channel.get_ivr_client() domain = self.channel.callback_domain from temba.ivr.clients import IVRException from temba.flows.models import ActionLog, FlowRun if client: try: url = "https://%s%s" % (domain, reverse("ivr.ivrcall_handle", args=[self.pk])) if qs: # pragma: no cover url = "%s?%s" % (url, qs) tel = None # if we are working with a test contact, look for user settings if self.contact.is_test: user_settings = self.created_by.get_settings() if user_settings.tel: tel = user_settings.tel run = FlowRun.objects.filter(connection=self) if run: ActionLog.create(run[0], "Placing test call to %s" % user_settings.get_tel_formatted()) if not tel: tel_urn = self.contact_urn tel = tel_urn.path client.start_call(self, to=tel, from_=self.channel.address, status_callback=url) except IVRException as e: import traceback traceback.print_exc() if self.contact.is_test: run = FlowRun.objects.filter(connection=self) ActionLog.create(run[0], "Call ended. %s" % str(e)) except Exception as e: # pragma: no cover import traceback traceback.print_exc() ChannelLog.log_ivr_interaction( self, "Call failed unexpectedly", HttpEvent(method="INTERNAL", url=None, response_body=str(e)) ) self.status = self.FAILED self.save() if self.contact.is_test: run = FlowRun.objects.filter(connection=self) ActionLog.create(run[0], "Call ended.")
def trigger_flow_event(cls, webhook_url, flow, run, node_uuid, contact, event, action='POST', resthook=None): org = flow.org api_user = get_api_user() json_time = datetime_to_str(timezone.now()) # get the results for this contact results = flow.get_results(contact) values = [] if results and results[0]: values = results[0]['values'] for value in values: value['time'] = datetime_to_str(value['time']) value['value'] = unicode(value['value']) # if the action is on the first node # we might not have an sms (or channel) yet channel = None text = None contact_urn = contact.get_urn() if event: text = event.text channel = event.channel contact_urn = event.contact_urn if channel: channel_id = channel.pk else: channel_id = -1 steps = [] for step in run.steps.prefetch_related( 'messages', 'broadcasts').order_by('arrived_on'): steps.append( dict(type=step.step_type, node=step.step_uuid, arrived_on=datetime_to_str(step.arrived_on), left_on=datetime_to_str(step.left_on), text=step.get_text(), value=step.rule_value)) data = dict(channel=channel_id, relayer=channel_id, flow=flow.id, flow_name=flow.name, flow_base_language=flow.base_language, run=run.id, text=text, step=unicode(node_uuid), phone=contact.get_urn_display(org=org, scheme=TEL_SCHEME, formatted=False), contact=contact.uuid, urn=unicode(contact_urn), values=json.dumps(values), steps=json.dumps(steps), time=json_time) if not action: action = 'POST' webhook_event = WebHookEvent.objects.create(org=org, event=FLOW, channel=channel, data=json.dumps(data), try_count=1, action=action, resthook=resthook, created_by=api_user, modified_by=api_user) status_code = -1 message = "None" body = None # webhook events fire immediately since we need the results back try: # only send webhooks when we are configured to, otherwise fail if not settings.SEND_WEBHOOKS: raise Exception( "!! Skipping WebHook send, SEND_WEBHOOKS set to False") # no url, bail! if not webhook_url: raise Exception("No webhook_url specified, skipping send") # some hosts deny generic user agents, use Temba as our user agent if action == 'GET': response = requests.get(webhook_url, headers=TEMBA_HEADERS, timeout=10) else: response = requests.post(webhook_url, data=data, headers=TEMBA_HEADERS, timeout=10) response_text = response.text body = response.text status_code = response.status_code if response.status_code == 200 or response.status_code == 201: try: response_json = json.loads(response_text) # only update if we got a valid JSON dictionary or list if not isinstance(response_json, dict) and not isinstance( response_json, list): raise ValueError( "Response must be a JSON dictionary or list, ignoring response." ) run.update_fields(response_json) message = "Webhook called successfully." except ValueError as e: message = "Response must be a JSON dictionary, ignoring response." webhook_event.status = COMPLETE else: webhook_event.status = FAILED message = "Got non 200 response (%d) from webhook." % response.status_code raise Exception("Got non 200 response (%d) from webhook." % response.status_code) except Exception as e: import traceback traceback.print_exc() webhook_event.status = FAILED message = "Error calling webhook: %s" % unicode(e) finally: webhook_event.save() # make sure our message isn't too long if message: message = message[:255] result = WebHookResult.objects.create(event=webhook_event, url=webhook_url, status_code=status_code, body=body, message=message, data=urlencode(data, doseq=True), created_by=api_user, modified_by=api_user) # if this is a test contact, add an entry to our action log if run.contact.is_test: from temba.flows.models import ActionLog log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % ( reverse('api.log_read', args=[webhook_event.pk ]), status_code) ActionLog.create(run, log_txt, safe=True) return result
def trigger_flow_webhook(cls, run, webhook_url, node_uuid, msg, action='POST', resthook=None, headers=None): flow = run.flow contact = run.contact org = flow.org channel = msg.channel if msg else None contact_urn = msg.contact_urn if msg else contact.get_urn() contact_dict = dict(uuid=contact.uuid, name=contact.name) if contact_urn: contact_dict['urn'] = contact_urn.urn post_data = { 'contact': contact_dict, 'flow': dict(name=flow.name, uuid=flow.uuid), 'path': run.get_path(), 'results': run.get_results(), 'run': dict(uuid=six.text_type(run.uuid), created_on=run.created_on.isoformat()) } if msg and msg.id > 0: post_data['input'] = dict( urn=msg.contact_urn.urn if msg.contact_urn else None, text=msg.text, attachments=(msg.attachments or [])) if channel: post_data['channel'] = dict(name=channel.name, uuid=channel.uuid) api_user = get_api_user() if not action: # pragma: needs cover action = 'POST' webhook_event = cls.objects.create(org=org, event=cls.TYPE_FLOW, channel=channel, data=json.dumps(post_data), run=run, try_count=1, action=action, resthook=resthook, created_by=api_user, modified_by=api_user) status_code = -1 message = "None" body = None start = time.time() # webhook events fire immediately since we need the results back try: # no url, bail! if not webhook_url: raise ValueError("No webhook_url specified, skipping send") # only send webhooks when we are configured to, otherwise fail if settings.SEND_WEBHOOKS: requests_headers = http_headers(extra=headers) # some hosts deny generic user agents, use Temba as our user agent if action == 'GET': response = requests.get(webhook_url, headers=requests_headers, timeout=10) else: requests_headers['Content-type'] = 'application/json' response = requests.post(webhook_url, data=json.dumps(post_data), headers=requests_headers, timeout=10) body = response.text if body: body = body.strip() status_code = response.status_code else: print("!! Skipping WebHook send, SEND_WEBHOOKS set to False") body = 'Skipped actual send' status_code = 200 # process the webhook response try: response_json = json.loads(body, object_pairs_hook=OrderedDict) # only update if we got a valid JSON dictionary or list if not isinstance(response_json, dict) and not isinstance( response_json, list): raise ValueError( "Response must be a JSON dictionary or list, ignoring response." ) run.update_fields(response_json) message = "Webhook called successfully." except ValueError: message = "Response must be a JSON dictionary, ignoring response." if 200 <= status_code < 300: webhook_event.status = cls.STATUS_COMPLETE else: webhook_event.status = cls.STATUS_FAILED message = "Got non 200 response (%d) from webhook." % response.status_code raise Exception("Got non 200 response (%d) from webhook." % response.status_code) except Exception as e: import traceback traceback.print_exc() webhook_event.status = cls.STATUS_FAILED message = "Error calling webhook: %s" % six.text_type(e) finally: webhook_event.save(update_fields=('status', )) # make sure our message isn't too long if message: message = message[:255] request_time = (time.time() - start) * 1000 contact = None if webhook_event.run: contact = webhook_event.run.contact result = WebHookResult.objects.create(event=webhook_event, contact=contact, url=webhook_url, status_code=status_code, body=body, message=message, data=json.dumps(post_data), request_time=request_time, created_by=api_user, modified_by=api_user) # if this is a test contact, add an entry to our action log if run.contact.is_test: log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % ( reverse('api.log_read', args=[webhook_event.pk ]), status_code) ActionLog.create(run, log_txt, safe=True) return result
def trigger_flow_event(cls, webhook_url, flow, run, node, contact, event, action="POST"): org = flow.org api_user = get_api_user() # no-op if no webhook configured if not webhook_url: return json_time = datetime_to_str(timezone.now()) # get the results for this contact results = flow.get_results(contact) values = [] if results and results[0]: values = results[0]["values"] for value in values: value["time"] = datetime_to_str(value["time"]) value["value"] = unicode(value["value"]) # if the action is on the first node # we might not have an sms (or channel) yet channel = None text = None if event: text = event.text channel = event.channel if channel: channel_id = channel.pk else: channel_id = -1 steps = [] for step in run.steps.all().order_by("arrived_on"): steps.append( dict( type=step.step_type, node=step.step_uuid, arrived_on=datetime_to_str(step.arrived_on), left_on=datetime_to_str(step.left_on), text=step.get_text(), value=step.rule_value, ) ) data = dict( channel=channel_id, relayer=channel_id, flow=flow.id, run=run.id, text=text, step=unicode(node.uuid), phone=contact.get_urn_display(org=org, scheme=TEL_SCHEME, full=True), values=json.dumps(values), steps=json.dumps(steps), time=json_time, ) if not action: action = "POST" webhook_event = WebHookEvent.objects.create( org=org, event=FLOW, channel=channel, data=json.dumps(data), try_count=1, action=action, created_by=api_user, modified_by=api_user, ) status_code = -1 message = "None" body = None # webhook events fire immediately since we need the results back try: # only send webhooks when we are configured to, otherwise fail if not settings.SEND_WEBHOOKS: raise Exception("!! Skipping WebHook send, SEND_WEBHOOKS set to False") # some hosts deny generic user agents, use Temba as our user agent if action == "GET": response = requests.get(webhook_url, headers=TEMBA_HEADERS, timeout=10) else: response = requests.post(webhook_url, data=data, headers=TEMBA_HEADERS, timeout=10) response_text = response.text body = response.text status_code = response.status_code if response.status_code == 200 or response.status_code == 201: try: response_json = json.loads(response_text) # only update if we got a valid JSON dictionary if not isinstance(response_json, dict): raise ValueError("Response must be a JSON dictionary, ignoring response.") run.update_fields(response_json) message = "Webhook called successfully." except ValueError as e: message = "Response must be a JSON dictionary, ignoring response." webhook_event.status = COMPLETE else: webhook_event.status = FAILED message = "Got non 200 response (%d) from webhook." % response.status_code raise Exception("Got non 200 response (%d) from webhook." % response.status_code) except Exception as e: import traceback traceback.print_exc() webhook_event.status = FAILED message = "Error calling webhook: %s" % unicode(e) finally: webhook_event.save() result = WebHookResult.objects.create( event=webhook_event, url=webhook_url, status_code=status_code, body=body, message=message, data=urlencode(data, doseq=True), created_by=api_user, modified_by=api_user, ) # if this is a test contact, add an entry to our action log if run.contact.is_test: from temba.flows.models import ActionLog log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % ( reverse("api.log_read", args=[webhook_event.pk]), status_code, ) ActionLog.create(run, log_txt, safe=True) return result
def trigger_flow_webhook(cls, run, webhook_url, ruleset, msg, action="POST", resthook=None, headers=None): flow = run.flow contact = run.contact org = flow.org channel = msg.channel if msg else None contact_urn = msg.contact_urn if (msg and msg.contact_urn) else contact.get_urn() contact_dict = dict(uuid=contact.uuid, name=contact.name) if contact_urn: contact_dict["urn"] = contact_urn.urn post_data = { "contact": contact_dict, "flow": dict(name=flow.name, uuid=flow.uuid, revision=flow.revisions.order_by("revision").last().revision), "path": run.path, "results": run.results, "run": dict(uuid=str(run.uuid), created_on=run.created_on.isoformat()), } if msg and msg.id > 0: post_data["input"] = dict( urn=msg.contact_urn.urn if msg.contact_urn else None, text=msg.text, attachments=(msg.attachments or []), ) if channel: post_data["channel"] = dict(name=channel.name, uuid=channel.uuid) api_user = get_api_user() if not action: # pragma: needs cover action = "POST" webhook_event = cls.objects.create( org=org, event=cls.TYPE_FLOW, channel=channel, data=post_data, run=run, try_count=1, action=action, resthook=resthook, created_by=api_user, modified_by=api_user, ) status_code = -1 message = "None" body = None start = time.time() # webhook events fire immediately since we need the results back try: # no url, bail! if not webhook_url: raise ValueError("No webhook_url specified, skipping send") # only send webhooks when we are configured to, otherwise fail if settings.SEND_WEBHOOKS: requests_headers = http_headers(extra=headers) # some hosts deny generic user agents, use Temba as our user agent if action == "GET": response = requests.get(webhook_url, headers=requests_headers, timeout=10) else: requests_headers["Content-type"] = "application/json" response = requests.post( webhook_url, data=json.dumps(post_data), headers=requests_headers, timeout=10 ) body = response.text if body: body = body.strip() status_code = response.status_code else: print("!! Skipping WebHook send, SEND_WEBHOOKS set to False") body = "Skipped actual send" status_code = 200 if ruleset: run.update_fields({Flow.label_to_slug(ruleset.label): body}, do_save=False) new_extra = {} # process the webhook response try: response_json = json.loads(body) # only update if we got a valid JSON dictionary or list if not isinstance(response_json, dict) and not isinstance(response_json, list): raise ValueError("Response must be a JSON dictionary or list, ignoring response.") new_extra = response_json message = "Webhook called successfully." except ValueError: message = "Response must be a JSON dictionary, ignoring response." run.update_fields(new_extra) if 200 <= status_code < 300: webhook_event.status = cls.STATUS_COMPLETE else: webhook_event.status = cls.STATUS_FAILED message = "Got non 200 response (%d) from webhook." % response.status_code raise Exception("Got non 200 response (%d) from webhook." % response.status_code) except Exception as e: import traceback traceback.print_exc() webhook_event.status = cls.STATUS_FAILED message = "Error calling webhook: %s" % str(e) finally: webhook_event.save(update_fields=("status",)) # make sure our message isn't too long if message: message = message[:255] request_time = (time.time() - start) * 1000 contact = None if webhook_event.run: contact = webhook_event.run.contact result = WebHookResult.objects.create( event=webhook_event, contact=contact, url=webhook_url, status_code=status_code, body=body, message=message, data=post_data, request_time=request_time, created_by=api_user, modified_by=api_user, ) # if this is a test contact, add an entry to our action log if run.contact.is_test: # pragma: no cover log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % ( reverse("api.log_read", args=[webhook_event.pk]), status_code, ) ActionLog.create(run, log_txt, safe=True) return result
def do_start_call(self, qs=None): client = self.channel.get_ivr_client() domain = self.channel.callback_domain from temba.ivr.clients import IVRException from temba.flows.models import ActionLog, FlowRun if client and domain: try: url = "https://%s%s" % (domain, reverse("ivr.ivrcall_handle", args=[self.pk])) if qs: # pragma: no cover url = "%s?%s" % (url, qs) tel = None # if we are working with a test contact, look for user settings if self.contact.is_test: user_settings = self.created_by.get_settings() if user_settings.tel: tel = user_settings.tel run = FlowRun.objects.filter(connection=self) if run: ActionLog.create(run[0], "Placing test call to %s" % user_settings.get_tel_formatted()) if not tel: tel_urn = self.contact_urn tel = tel_urn.path client.start_call(self, to=tel, from_=self.channel.address, status_callback=url) except IVRException as e: import traceback traceback.print_exc() if self.contact.is_test: run = FlowRun.objects.filter(connection=self) ActionLog.create(run[0], "Call ended. %s" % str(e)) except Exception as e: # pragma: no cover import traceback traceback.print_exc() ChannelLog.log_ivr_interaction( self, "Call failed unexpectedly", HttpEvent(method="INTERNAL", url=None, response_body=str(e)) ) self.status = self.FAILED self.save(update_fields=("status",)) if self.contact.is_test: run = FlowRun.objects.filter(connection=self) ActionLog.create(run[0], "Call ended.") # client or domain are not known else: ChannelLog.log_ivr_interaction( self, "Unknown client or domain", HttpEvent(method="INTERNAL", url=None, response_body=f"client={client} domain={domain}"), ) self.status = self.FAILED self.save(update_fields=("status",))
def trigger_flow_event(cls, run, webhook_url, node_uuid, msg, action='POST', resthook=None, header=None): flow = run.flow org = flow.org contact = run.contact api_user = get_api_user() json_time = datetime_to_str(timezone.now()) # get the results for this contact results = run.flow.get_results(run.contact) values = [] if results and results[0]: values = results[0]['values'] for value in values: value['time'] = datetime_to_str(value['time']) value['value'] = six.text_type(value['value']) if msg: text = msg.text attachments = msg.get_attachments() channel = msg.channel contact_urn = msg.contact_urn else: # if the action is on the first node we might not have an sms (or channel) yet channel = None text = None attachments = [] contact_urn = contact.get_urn() steps = [] for step in run.steps.prefetch_related('messages', 'broadcasts').order_by('arrived_on'): steps.append(dict(type=step.step_type, node=step.step_uuid, arrived_on=datetime_to_str(step.arrived_on), left_on=datetime_to_str(step.left_on), text=step.get_text(), value=step.rule_value)) data = dict(channel=channel.id if channel else -1, channel_uuid=channel.uuid if channel else None, relayer=channel.id if channel else -1, flow=flow.id, flow_uuid=flow.uuid, flow_name=flow.name, flow_base_language=flow.base_language, run=run.id, text=text, attachments=[a.url for a in attachments], step=six.text_type(node_uuid), phone=contact.get_urn_display(org=org, scheme=TEL_SCHEME, formatted=False), contact=contact.uuid, contact_name=contact.name, urn=six.text_type(contact_urn), values=json.dumps(values), steps=json.dumps(steps), time=json_time, header=header) if not action: # pragma: needs cover action = 'POST' webhook_event = cls.objects.create(org=org, event=cls.TYPE_FLOW, channel=channel, data=json.dumps(data), run=run, try_count=1, action=action, resthook=resthook, created_by=api_user, modified_by=api_user) status_code = -1 message = "None" body = None start = time.time() # webhook events fire immediately since we need the results back try: # no url, bail! if not webhook_url: raise Exception("No webhook_url specified, skipping send") # only send webhooks when we are configured to, otherwise fail if settings.SEND_WEBHOOKS: requests_headers = TEMBA_HEADERS if header: requests_headers.update(header) # some hosts deny generic user agents, use Temba as our user agent if action == 'GET': response = requests.get(webhook_url, headers=requests_headers, timeout=10) else: response = requests.post(webhook_url, data=data, headers=requests_headers, timeout=10) body = response.text if body: body = body.strip() status_code = response.status_code else: print("!! Skipping WebHook send, SEND_WEBHOOKS set to False") body = 'Skipped actual send' status_code = 200 # process the webhook response try: response_json = json.loads(body, object_pairs_hook=OrderedDict) # only update if we got a valid JSON dictionary or list if not isinstance(response_json, dict) and not isinstance(response_json, list): raise ValueError("Response must be a JSON dictionary or list, ignoring response.") run.update_fields(response_json) message = "Webhook called successfully." except ValueError: message = "Response must be a JSON dictionary, ignoring response." if 200 <= status_code < 300: webhook_event.status = cls.STATUS_COMPLETE else: webhook_event.status = cls.STATUS_FAILED message = "Got non 200 response (%d) from webhook." % response.status_code raise Exception("Got non 200 response (%d) from webhook." % response.status_code) except Exception as e: import traceback traceback.print_exc() webhook_event.status = cls.STATUS_FAILED message = "Error calling webhook: %s" % six.text_type(e) finally: webhook_event.save() # make sure our message isn't too long if message: message = message[:255] request_time = (time.time() - start) * 1000 contact = None if webhook_event.run: contact = webhook_event.run.contact result = WebHookResult.objects.create(event=webhook_event, contact=contact, url=webhook_url, status_code=status_code, body=body, message=message, data=urlencode(data, doseq=True), request_time=request_time, created_by=api_user, modified_by=api_user) # if this is a test contact, add an entry to our action log if run.contact.is_test: log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % (reverse('api.log_read', args=[webhook_event.pk]), status_code) ActionLog.create(run, log_txt, safe=True) return result
def trigger_flow_webhook(cls, run, webhook_url, ruleset, msg, action="POST", resthook=None, headers=None): flow = run.flow contact = run.contact org = flow.org channel = msg.channel if msg else None contact_urn = msg.contact_urn if ( msg and msg.contact_urn) else contact.get_urn() contact_dict = dict(uuid=contact.uuid, name=contact.name) if contact_urn: contact_dict["urn"] = contact_urn.urn post_data = { "contact": contact_dict, "flow": dict(name=flow.name, uuid=flow.uuid, revision=flow.revisions.order_by("revision").last().revision), "path": run.path, "results": run.results, "run": dict(uuid=str(run.uuid), created_on=run.created_on.isoformat()), } if msg and msg.id > 0: post_data["input"] = dict( urn=msg.contact_urn.urn if msg.contact_urn else None, text=msg.text, attachments=(msg.attachments or []), ) if channel: post_data["channel"] = dict(name=channel.name, uuid=channel.uuid) api_user = get_api_user() if not action: # pragma: needs cover action = "POST" webhook_event = cls.objects.create( org=org, event=cls.TYPE_FLOW, channel=channel, data=post_data, run=run, try_count=1, action=action, resthook=resthook, created_by=api_user, modified_by=api_user, ) status_code = -1 message = "None" body = None start = time.time() # webhook events fire immediately since we need the results back try: # no url, bail! if not webhook_url: raise ValueError("No webhook_url specified, skipping send") # only send webhooks when we are configured to, otherwise fail if settings.SEND_WEBHOOKS: requests_headers = http_headers(extra=headers) # some hosts deny generic user agents, use Temba as our user agent if action == "GET": response = requests.get(webhook_url, headers=requests_headers, timeout=10) else: requests_headers["Content-type"] = "application/json" response = requests.post(webhook_url, data=json.dumps(post_data), headers=requests_headers, timeout=10) body = response.text if body: body = body.strip() status_code = response.status_code else: print("!! Skipping WebHook send, SEND_WEBHOOKS set to False") body = "Skipped actual send" status_code = 200 if ruleset: run.update_fields({Flow.label_to_slug(ruleset.label): body}, do_save=False) new_extra = {} # process the webhook response try: response_json = json.loads(body) # only update if we got a valid JSON dictionary or list if not isinstance(response_json, dict) and not isinstance( response_json, list): raise ValueError( "Response must be a JSON dictionary or list, ignoring response." ) new_extra = response_json message = "Webhook called successfully." except ValueError: message = "Response must be a JSON dictionary, ignoring response." run.update_fields(new_extra) if 200 <= status_code < 300: webhook_event.status = cls.STATUS_COMPLETE else: webhook_event.status = cls.STATUS_FAILED message = "Got non 200 response (%d) from webhook." % response.status_code raise Exception("Got non 200 response (%d) from webhook." % response.status_code) except Exception as e: import traceback traceback.print_exc() webhook_event.status = cls.STATUS_FAILED message = "Error calling webhook: %s" % str(e) finally: webhook_event.save(update_fields=("status", )) # make sure our message isn't too long if message: message = message[:255] request_time = (time.time() - start) * 1000 contact = None if webhook_event.run: contact = webhook_event.run.contact result = WebHookResult.objects.create( event=webhook_event, contact=contact, url=webhook_url, status_code=status_code, body=body, message=message, data=post_data, request_time=request_time, created_by=api_user, modified_by=api_user, ) # if this is a test contact, add an entry to our action log if run.contact.is_test: # pragma: no cover log_txt = "Triggered <a href='%s' target='_log'>webhook event</a> - %d" % ( reverse("api.log_read", args=[webhook_event.pk]), status_code, ) ActionLog.create(run, log_txt, safe=True) return result