def send(self, channel, msg, text): # requests are signed with a key built as follows: # signing_key = md5(username|password|from|to|msg|key|current_date) # where current_date is in the format: d/m/y H payload = {'from': channel.address.lstrip('+'), 'to': msg.urn_path.lstrip('+'), 'username': channel.config[Channel.CONFIG_USERNAME], 'password': channel.config[Channel.CONFIG_PASSWORD], 'msg': text} # build our send URL url = channel.config[Channel.CONFIG_SEND_URL] + "?" + urlencode(payload) start = time.time() event = HttpEvent('GET', url) try: # these guys use a self signed certificate response = requests.get(url, headers=http_headers(), timeout=15, verify=False) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if response.status_code != 200 and response.status_code != 201 and response.status_code != 202: raise SendException("Got non-200 response [%d] from API" % response.status_code, event=event, start=start) Channel.success(channel, msg, WIRED, start, event=event)
def send(self, channel, msg, text): # build our message dict params = dict(origin=channel.address.lstrip('+'), sms_content=text, destinations=msg.urn_path.lstrip('+'), ybsacctno=channel.config['username'], password=channel.config['password']) log_params = params.copy() log_params['password'] = '******' * len(log_params['password']) start = time.time() failed = False fatal = False events = [] for send_url in [YO_API_URL_1, YO_API_URL_2, YO_API_URL_3]: url = send_url + '?' + urlencode(params) log_url = send_url + '?' + urlencode(log_params) event = HttpEvent('GET', log_url) events.append(event) failed = False try: response = requests.get(url, headers=http_headers(), timeout=5) event.status_code = response.status_code event.response_body = response.text response_qs = urlparse.parse_qs(response.text) except Exception: failed = True if not failed and response.status_code != 200 and response.status_code != 201: failed = True # if it wasn't successfully delivered, throw if not failed and response_qs.get('ybs_autocreate_status', [''])[0] != 'OK': failed = True # check if we failed permanently (they blocked us) if failed and response_qs.get('ybs_autocreate_message', [''])[0].find('BLACKLISTED') >= 0: contact = Contact.objects.get(id=msg.contact) contact.stop(contact.modified_by) fatal = True break # if we sent the message, then move on if not failed: break if failed: raise SendException("Received error from Yo! API", events=events, fatal=fatal, start=start) Channel.success(channel, msg, SENT, start, events=events)
def send(self, channel, msg, text): callback_domain = channel.callback_domain payload = { 'accountid': channel.config[Channel.CONFIG_USERNAME], 'password': channel.config[Channel.CONFIG_PASSWORD], 'text': text, 'to': msg.urn_path, 'ret_id': msg.id, 'datacoding': 8, 'userdata': 'textit', 'ret_url': 'https://%s%s' % (callback_domain, reverse('handlers.hcnx_handler', args=['status', channel.uuid])), 'ret_mo_url': 'https://%s%s' % (callback_domain, reverse('handlers.hcnx_handler', args=['receive', channel.uuid])) } # build our send URL url = 'https://highpushfastapi-v2.hcnx.eu/api' + '?' + urlencode(payload) log_payload = urlencode(payload) start = time.time() event = HttpEvent('GET', url, log_payload) try: response = requests.get(url, headers=http_headers(), timeout=30) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if response.status_code != 200 and response.status_code != 201 and response.status_code != 202: raise SendException("Got non-200 response [%d] from API" % response.status_code, event=event, start=start) Channel.success(channel, msg, WIRED, start, event=event)
def form_valid(self, form, *args, **kwargs): data = form.cleaned_data auth_id = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_ID, None) auth_token = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_TOKEN, None) try: params = dict(country_iso=data["country"], pattern=data.get("pattern")) url = f"https://api.plivo.com/v1/Account/{auth_id}/PhoneNumber/?{urlencode(params)}" headers = http_headers(extra={"Content-Type": "application/json"}) response = requests.get(url, headers=headers, auth=(auth_id, auth_token)) if response.status_code == 200: response_data = response.json() results_numbers = [ "+" + number_dict["number"] for number_dict in response_data["objects"] ] else: return JsonResponse({"error": response.text}) numbers = [] for number in results_numbers: numbers.append( phonenumbers.format_number( phonenumbers.parse(number, None), phonenumbers.PhoneNumberFormat.INTERNATIONAL)) return JsonResponse(numbers, safe=False) except Exception as e: return JsonResponse({"error": str(e)})
def send(self, channel, msg, text): payload = { 'address': msg.urn_path.lstrip('+'), 'message': text, 'passphrase': channel.config['passphrase'], 'app_id': channel.config['app_id'], 'app_secret': channel.config['app_secret'] } url = 'https://devapi.globelabs.com.ph/smsmessaging/v1/outbound/%s/requests' % channel.address event = HttpEvent('POST', url, json.dumps(payload)) start = time.time() try: response = requests.post(url, data=payload, headers=http_headers(), timeout=5) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if response.status_code != 200 and response.status_code != 201: # pragma: no cover raise SendException("Got non-200 response [%d] from API" % response.status_code, event=event, start=start) # parse our response response.json() Channel.success(channel, msg, WIRED, start, event=event)
def send(self, channel, msg, text): # determine our encoding encoding, text = Channel.determine_encoding(text, replace=True) # if this looks like unicode, ask macrokiosk to send as unicode if encoding == Encoding.UNICODE: message_type = 5 else: message_type = 0 # strip a leading + recipient = msg.urn_path[1:] if msg.urn_path.startswith( '+') else msg.urn_path data = { 'user': channel.config[Channel.CONFIG_USERNAME], 'pass': channel.config[Channel.CONFIG_PASSWORD], 'to': recipient, 'text': text, 'from': channel.config[Channel.CONFIG_MACROKIOSK_SENDER_ID], 'servid': channel.config[Channel.CONFIG_MACROKIOSK_SERVICE_ID], 'type': message_type } url = 'https://www.etracker.cc/bulksms/send' payload = json.dumps(data) headers = http_headers(extra={ 'Content-Type': 'application/json', 'Accept': 'application/json' }) event = HttpEvent('POST', url, payload) start = time.time() try: response = requests.post(url, json=data, headers=headers, timeout=30) event.status_code = response.status_code event.response_body = response.text external_id = response.json().get('msgid', None) except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if response.status_code not in [200, 201, 202]: raise SendException("Got non-200 response [%d] from API" % response.status_code, event=event, start=start) Channel.success(channel, msg, WIRED, start, event=event, external_id=external_id)
def send(self, channel, msg, text): # strip a leading + mobile = msg.urn_path[1:] if msg.urn_path.startswith('+') else msg.urn_path payload = { 'user': channel.config[Channel.CONFIG_USERNAME], 'pass': channel.config[Channel.CONFIG_PASSWORD], 'mobile': mobile, 'content': text, } url = 'http://smail.smscentral.com.np/bp/ApiSms.php' log_payload = urlencode(payload) event = HttpEvent('POST', url, log_payload) start = time.time() try: response = requests.post(url, data=payload, headers=http_headers(), timeout=30) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if response.status_code != 200 and response.status_code != 201 and response.status_code != 202: raise SendException("Got non-200 response [%d] from API" % response.status_code, event=event, start=start) Channel.success(channel, msg, WIRED, start, event=event)
def send(self, channel, msg, text): # http://175.103.48.29:28078/testing/smsmt.php? # userid=xxx # &password=xxxx # &original=6282881134567 # &sendto=628159152565 # &messagetype=0 # &messageid=1897869768 # &message=Test+Normal+Single+Message&dcs=0 # &udhl=0&charset=utf-8 # url = HUB9_ENDPOINT payload = dict(userid=channel.config['username'], password=channel.config['password'], original=channel.address.lstrip('+'), sendto=msg.urn_path.lstrip('+'), messageid=msg.id, message=text, dcs=0, udhl=0) # build up our querystring and send it as a get send_url = "%s?%s" % (url, urlencode(payload)) payload['password'] = '******' * len(payload['password']) masked_url = "%s?%s" % (url, urlencode(payload)) event = HttpEvent('GET', masked_url) start = time.time() try: response = requests.get(send_url, headers=http_headers(), timeout=15) event.status_code = response.status_code event.response_body = response.text if not response: # pragma: no cover raise SendException("Unable to send message", event=event, start=start) if response.status_code != 200 and response.status_code != 201: raise SendException("Received non 200 status: %d" % response.status_code, event=event, start=start) # if it wasn't successfully delivered, throw if response.text != "000": # pragma: no cover error = "Unknown error" if response.text == "001": error = "Error 001: Authentication Error" elif response.text == "101": error = "Error 101: Account expired or invalid parameters" raise SendException(error, event=event, start=start) Channel.success(channel, msg, SENT, start, event=event) except SendException as e: raise e except Exception as e: # pragma: no cover reason = "Unknown error" try: if e.message and e.message.reason: reason = e.message.reason except Exception: pass raise SendException(u"Unable to send message: %s" % six.text_type(reason)[:64], event=event, start=start)
def get_existing_numbers(self, org): auth_id = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_ID, None) auth_token = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_TOKEN, None) headers = http_headers(extra={"Content-Type": "application/json"}) response = requests.get("https://api.plivo.com/v1/Account/%s/Number/" % auth_id, headers=headers, auth=(auth_id, auth_token)) account_numbers = [] if response.status_code == 200: data = response.json() for number_dict in data["objects"]: region = number_dict["region"] country_name = region.split(",")[-1].strip().title() country = pycountry.countries.get(name=country_name).alpha_2 if len(number_dict["number"]) <= 6: phone_number = number_dict["number"] else: parsed = phonenumbers.parse("+" + number_dict["number"], None) phone_number = phonenumbers.format_number( parsed, phonenumbers.PhoneNumberFormat.INTERNATIONAL) account_numbers.append( dict(number=phone_number, country=country)) return account_numbers
def deactivate(self, channel): config = channel.config requests.delete( "https://api.plivo.com/v1/Account/%s/Application/%s/" % (config[Channel.CONFIG_PLIVO_AUTH_ID], config[Channel.CONFIG_PLIVO_APP_ID]), auth=(config[Channel.CONFIG_PLIVO_AUTH_ID], config[Channel.CONFIG_PLIVO_AUTH_TOKEN]), headers=http_headers(extra={"Content-Type": "application/json"}), )
def deactivate(self, channel): config = channel.config requests.delete( "https://api.plivo.com/v1/Account/%s/Application/%s/" % (config[Channel.CONFIG_PLIVO_AUTH_ID], config[Channel.CONFIG_PLIVO_APP_ID]), auth=(config[Channel.CONFIG_PLIVO_AUTH_ID], config[Channel.CONFIG_PLIVO_AUTH_TOKEN]), headers=http_headers(extra={"Content-Type": "application/json"}), )
def claim_number(self, user, phone_number, country, role): auth_id = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_ID, None) auth_token = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_TOKEN, None) org = user.get_org() plivo_uuid = generate_uuid() callback_domain = org.get_brand_domain() app_name = "%s/%s" % (callback_domain.lower(), plivo_uuid) message_url = "https://" + callback_domain + "%s" % reverse('handlers.plivo_handler', args=['receive', plivo_uuid]) answer_url = "https://" + settings.AWS_BUCKET_DOMAIN + "/plivo_voice_unavailable.xml" headers = http_headers(extra={'Content-Type': "application/json"}) create_app_url = "https://api.plivo.com/v1/Account/%s/Application/" % auth_id response = requests.post(create_app_url, json=dict(app_name=app_name, answer_url=answer_url, message_url=message_url), headers=headers, auth=(auth_id, auth_token)) if response.status_code in [201, 200, 202]: plivo_app_id = response.json()['app_id'] else: # pragma: no cover plivo_app_id = None plivo_config = {Channel.CONFIG_PLIVO_AUTH_ID: auth_id, Channel.CONFIG_PLIVO_AUTH_TOKEN: auth_token, Channel.CONFIG_PLIVO_APP_ID: plivo_app_id, Channel.CONFIG_CALLBACK_DOMAIN: org.get_brand_domain()} plivo_number = phone_number.strip('+ ').replace(' ', '') response = requests.get("https://api.plivo.com/v1/Account/%s/Number/%s/" % (auth_id, plivo_number), headers=headers, auth=(auth_id, auth_token)) if response.status_code != 200: response = requests.post("https://api.plivo.com/v1/Account/%s/PhoneNumber/%s/" % (auth_id, plivo_number), headers=headers, auth=(auth_id, auth_token)) if response.status_code != 201: # pragma: no cover raise Exception(_("There was a problem claiming that number, please check the balance on your account.")) response = requests.get("https://api.plivo.com/v1/Account/%s/Number/%s/" % (auth_id, plivo_number), headers=headers, auth=(auth_id, auth_token)) if response.status_code == 200: response = requests.post("https://api.plivo.com/v1/Account/%s/Number/%s/" % (auth_id, plivo_number), json=dict(app_id=plivo_app_id), headers=headers, auth=(auth_id, auth_token)) if response.status_code != 202: # pragma: no cover raise Exception(_("There was a problem updating that number, please try again.")) phone_number = '+' + plivo_number phone = phonenumbers.format_number(phonenumbers.parse(phone_number, None), phonenumbers.PhoneNumberFormat.NATIONAL) channel = Channel.create(org, user, country, 'PL', name=phone, address=phone_number, config=plivo_config, uuid=plivo_uuid) analytics.track(user.username, 'temba.channel_claim_plivo', dict(number=phone_number)) return channel
def send(self, channel, msg, text): url = 'http://bulk.startmobile.com.ua/clients.php' post_body = u""" <message> <service id="single" source=$$FROM$$ validity=$$VALIDITY$$/> <to>$$TO$$</to> <body content-type="plain/text" encoding="plain">$$BODY$$</body> </message> """ post_body = post_body.replace("$$FROM$$", quoteattr(channel.address)) # tell Start to attempt to deliver this message for up to 12 hours post_body = post_body.replace("$$VALIDITY$$", quoteattr("+12 hours")) post_body = post_body.replace("$$TO$$", escape(msg.urn_path)) post_body = post_body.replace("$$BODY$$", escape(text)) event = HttpEvent('POST', url, post_body) post_body = post_body.encode('utf8') start = time.time() try: headers = http_headers( extra={'Content-Type': 'application/xml; charset=utf8'}) response = requests.post( url, data=post_body, headers=headers, auth=(channel.config[Channel.CONFIG_USERNAME], channel.config[Channel.CONFIG_PASSWORD]), timeout=30) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if (response.status_code != 200 and response.status_code != 201 ) or response.text.find("error") >= 0: raise SendException("Error Sending Message", event=event, start=start) # parse out our id, this is XML but we only care about the id external_id = None start_idx = response.text.find("<id>") end_idx = response.text.find("</id>") if end_idx > start_idx > 0: external_id = response.text[start_idx + 4:end_idx] Channel.success(channel, msg, WIRED, start, event=event, external_id=external_id)
def send(self, channel, msg, text): start = time.time() url = 'https://fcm.googleapis.com/fcm/send' title = channel.config.get('FCM_TITLE') data = { 'data': { 'type': 'rapidpro', 'title': title, 'message': text, 'message_id': msg.id }, 'content_available': False, 'to': msg.auth, 'priority': 'high' } if channel.config.get('FCM_NOTIFICATION'): data['notification'] = {'title': title, 'body': text} data['content_available'] = True payload = json.dumps(data) headers = http_headers( extra={ 'Content-Type': 'application/json', 'Authorization': 'key=%s' % channel.config.get('FCM_KEY') }) event = HttpEvent('POST', url, payload) try: response = requests.post(url, data=payload, headers=headers, timeout=5) result = json.loads( response.text) if response.status_code == 200 else None event.status_code = response.status_code event.response_body = response.text except Exception as e: # pragma: no cover raise SendException(six.text_type(e), event, start=start) if result and 'success' in result and result.get('success') == 1: external_id = result.get('multicast_id') Channel.success(channel, msg, WIRED, start, events=[event], external_id=external_id) else: raise SendException( "Got non-200 response [%d] from Firebase Cloud Messaging" % response.status_code, event, start=start)
def send(self, channel, msg, text): # determine our encoding encoding, text = Channel.determine_encoding(text, replace=True) # if this looks like unicode, ask clickatell to send as unicode if encoding == Encoding.UNICODE: unicode_switch = 1 else: unicode_switch = 0 url = 'https://api.clickatell.com/http/sendmsg' payload = { 'api_id': channel.config[Channel.CONFIG_API_ID], 'user': channel.config[Channel.CONFIG_USERNAME], 'password': channel.config[Channel.CONFIG_PASSWORD], 'from': channel.address.lstrip('+'), 'concat': 3, 'callback': 7, 'mo': 1, 'unicode': unicode_switch, 'to': msg.urn_path.lstrip('+'), 'text': text } event = HttpEvent('GET', url + "?" + urlencode(payload)) start = time.time() try: response = requests.get(url, params=payload, headers=http_headers(), timeout=5) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if response.status_code != 200 and response.status_code != 201 and response.status_code != 202: raise SendException("Got non-200 response [%d] from API" % response.status_code, event=event, start=start) # parse out the external id for the message, comes in the format: "ID: id12312312312" external_id = None if response.text.startswith("ID: "): external_id = response.text[4:] Channel.success(channel, msg, WIRED, start, event=event, external_id=external_id)
def send(self, channel, msg, text): payload = dict(username=channel.config['username'], to=msg.urn_path, message=text) # if this isn't a shared shortcode, send the from address if not channel.config.get('is_shared', False): payload['from'] = channel.address headers = http_headers( dict(Accept='application/json', apikey=channel.config['api_key'])) api_url = "https://api.africastalking.com/version1/messaging" event = HttpEvent('POST', api_url, urlencode(payload)) start = time.time() try: response = requests.post(api_url, data=payload, headers=headers, timeout=5) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(u"Unable to send message: %s" % six.text_type(e), event=event, start=start) if response.status_code != 200 and response.status_code != 201: raise SendException("Got non-200 response from API: %d" % response.status_code, event=event, start=start) response_data = response.json() # grab the status out of our response status = response_data['SMSMessageData']['Recipients'][0]['status'] if status != 'Success': raise SendException("Got non success status from API: %s" % status, event=event, start=start) # set our external id so we know when it is actually sent, this is missing in cases where # it wasn't sent, in which case we'll become an errored message external_id = response_data['SMSMessageData']['Recipients'][0][ 'messageId'] Channel.success(channel, msg, SENT, start, event=event, external_id=external_id)
def send(self, channel, msg, text): url = "https://api.infobip.com/sms/1/text/advanced" username = channel.config['username'] password = channel.config['password'] encoded_auth = base64.b64encode(username + ":" + password) headers = http_headers(extra={ 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': 'Basic %s' % encoded_auth }) # the event url InfoBip will forward delivery reports to status_url = 'https://%s%s' % (channel.callback_domain, reverse('courier.ib', args=[channel.uuid, 'delivered'])) payload = {"messages": [ { "from": channel.address.lstrip('+'), "destinations": [ {"to": msg.urn_path.lstrip('+'), "messageId": msg.id} ], "text": text, "notifyContentType": "application/json", "intermediateReport": True, "notifyUrl": status_url } ]} event = HttpEvent('POST', url, json.dumps(payload)) events = [event] start = time.time() try: response = requests.post(url, json=payload, headers=headers, timeout=5) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(u"Unable to send message: %s" % six.text_type(e), events=events, start=start) if response.status_code != 200 and response.status_code != 201: raise SendException("Received non 200 status: %d" % response.status_code, events=events, start=start) response_json = response.json() messages = response_json['messages'] # if it wasn't successfully delivered, throw if int(messages[0]['status']['groupId']) not in [1, 3]: raise SendException("Received error status: %s" % messages[0]['status']['description'], events=events, start=start) Channel.success(channel, msg, SENT, start, events=events)
def pre_process(self, *args, **kwargs): auth_id = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_ID, None) auth_token = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_TOKEN, None) headers = http_headers(extra={'Content-Type': "application/json"}) response = requests.get("https://api.plivo.com/v1/Account/%s/" % auth_id, headers=headers, auth=(auth_id, auth_token)) if response.status_code == 200: return None else: return HttpResponseRedirect(reverse('orgs.org_plivo_connect'))
def _request(self, url, method='GET', params=None, access_token=None): headers = http_headers(extra={'Authorization': 'Bearer ' + access_token} if access_token else {}) if method == 'POST_JSON': response = requests.post(url, json=params, headers=headers, timeout=15) elif method == 'POST': response = requests.post(url, data=params, headers=headers, timeout=15) else: response = requests.get(url, params=params, headers=headers, timeout=15) return response
def pre_process(self, *args, **kwargs): auth_id = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_ID, None) auth_token = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_TOKEN, None) headers = http_headers(extra={"Content-Type": "application/json"}) response = requests.get( "https://api.plivo.com/v1/Account/%s/" % auth_id, headers=headers, auth=(auth_id, auth_token) ) if response.status_code == 200: return None else: return HttpResponseRedirect(reverse("orgs.org_plivo_connect"))
def send(self, channel, msg, text): payload = { 'address': msg.urn_path, 'senderaddress': channel.address, 'message': text, } url = 'http://api.blackmyna.com/2/smsmessaging/outbound' external_id = None start = time.time() event = HttpEvent('POST', url, payload) try: response = requests.post( url, data=payload, headers=http_headers(), timeout=30, auth=(channel.config[Channel.CONFIG_USERNAME], channel.config[Channel.CONFIG_PASSWORD])) # parse our response, should be JSON that looks something like: # [{ # "recipient" : recipient_number_1, # "id" : Unique_identifier (universally unique identifier UUID) # }] event.status_code = response.status_code event.response_body = response.text response_json = response.json() # we only care about the first piece if response_json and len(response_json) > 0: external_id = response_json[0].get('id', None) except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if response.status_code != 200 and response.status_code != 201 and response.status_code != 202: # pragma: needs cover raise SendException("Got non-200 response [%d] from API" % response.status_code, event=event, start=start) Channel.success(channel, msg, WIRED, start, event=event, external_id=external_id)
def send(self, channel, msg, text): # Zenvia accepts messages via a GET # http://www.zenvia360.com.br/GatewayIntegration/msgSms.do?dispatch=send&account=temba& # code=abc123&to=5511996458779&msg=my message content&id=123&callbackOption=1 payload = dict(dispatch='send', account=channel.config['account'], code=channel.config['code'], msg=text, to=msg.urn_path, id=msg.id, callbackOption=1) zenvia_url = "http://www.zenvia360.com.br/GatewayIntegration/msgSms.do" headers = http_headers(extra={ 'Content-Type': "text/html", 'Accept-Charset': 'ISO-8859-1' }) event = HttpEvent('POST', zenvia_url, urlencode(payload)) start = time.time() try: response = requests.get(zenvia_url, params=payload, headers=headers, timeout=5) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(u"Unable to send message: %s" % six.text_type(e), event=event, start=start) if response.status_code != 200 and response.status_code != 201: raise SendException("Got non-200 response from API: %d" % response.status_code, event=event, start=start) response_code = int(response.text[:3]) if response_code != 0: raise Exception("Got non-zero response from Zenvia: %s" % response.text) Channel.success(channel, msg, WIRED, start, event=event)
def clean(self): access_token = self.cleaned_data.get('access_token') secret = self.cleaned_data.get('secret') headers = http_headers( extra={ 'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % access_token }) response = requests.get('https://api.line.me/v1/oauth/verify', headers=headers) content = response.json() if response.status_code != 200: raise ValidationError(content.get('error_desciption')) else: channel_id = content.get('channelId') channel_mid = content.get('mid') credentials = { 'channel_id': channel_id, 'channel_mid': channel_mid, 'channel_access_token': access_token, 'channel_secret': secret } existing = Channel.objects.filter( Q(config__contains=channel_id) | Q(config__contains=secret) | Q(config__contains=access_token), channel_type=self.channel_type.code, address=channel_mid, is_active=True).first() if existing: raise ValidationError( _("A channel with this configuration already exists.")) headers.pop('Content-Type') response_profile = requests.get( 'https://api.line.me/v1/profile', headers=headers) content_profile = json.loads(response_profile.content) credentials['profile'] = { 'picture_url': content_profile.get('pictureUrl'), 'display_name': content_profile.get('displayName') } return credentials
def send(self, channel, msg, text): url = 'https://chatapi.viber.com/pa/send_message' payload = { 'auth_token': channel.config['auth_token'], 'receiver': msg.urn_path, 'text': text, 'type': 'text', 'tracking_data': msg.id } event = HttpEvent('POST', url, json.dumps(payload)) start = time.time() headers = http_headers(extra={'Accept': 'application/json'}) try: response = requests.post(url, json=payload, headers=headers, timeout=5) event.status_code = response.status_code event.response_body = response.text response_json = response.json() except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if response.status_code not in [200, 201, 202]: raise SendException("Got non-200 response [%d] from API" % response.status_code, event=event, start=start) # success is 0, everything else is a failure if response_json['status'] != 0: raise SendException("Got non-0 status [%d] from API" % response_json['status'], event=event, fatal=True, start=start) external_id = response.json().get('message_token', None) Channel.success(channel, msg, WIRED, start, event=event, external_id=external_id)
def send(self, channel, msg, text): auth_id = channel.config[Channel.CONFIG_PLIVO_AUTH_ID] auth_token = channel.config[Channel.CONFIG_PLIVO_AUTH_TOKEN] url = 'https://api.plivo.com/v1/Account/%s/Message/' % auth_id status_url = "https://%s%s" % (channel.callback_domain, reverse('handlers.plivo_handler', args=['status', channel.uuid])) payload = { 'src': channel.address.lstrip('+'), 'dst': msg.urn_path.lstrip('+'), 'text': text, 'url': status_url, 'method': 'POST' } event = HttpEvent('POST', url, json.dumps(payload)) headers = http_headers(extra={'Content-Type': "application/json"}) start = time.time() try: # TODO: Grab real request and response here response = requests.post(url, json=payload, headers=headers, auth=(auth_id, auth_token)) event.status_code = response.status_code event.response_body = response.json() except Exception as e: # pragma: no cover raise SendException(six.text_type(e), event=event, start=start) if response.status_code not in [200, 201, 202]: # pragma: no cover raise SendException("Got non-200 response [%d] from API" % response.status_code, event=event, start=start) external_id = response.json()['message_uuid'][0] Channel.success(channel, msg, WIRED, start, event=event, external_id=external_id)
def send(self, channel, msg, text): payload = { 'id': str(msg.id), 'text': text, 'to': msg.urn_path, 'to_no_plus': msg.urn_path.lstrip('+'), 'from': channel.address, 'from_no_plus': channel.address.lstrip('+'), 'channel': str(channel.id) } # build our send URL url = Channel.replace_variables(channel.config[Channel.CONFIG_SEND_URL], payload) start = time.time() method = channel.config.get(Channel.CONFIG_SEND_METHOD, 'POST') content_type = channel.config.get(Channel.CONFIG_CONTENT_TYPE, Channel.CONTENT_TYPE_URLENCODED) headers = http_headers(extra={'Content-Type': Channel.CONTENT_TYPES[content_type]}) event = HttpEvent(method, url) if method in ('POST', 'PUT'): body = channel.config.get(Channel.CONFIG_SEND_BODY, Channel.CONFIG_DEFAULT_SEND_BODY) body = Channel.replace_variables(body, payload, content_type) event.request_body = body try: if method == 'POST': response = requests.post(url, data=body.encode('utf8'), headers=headers, timeout=5) elif method == 'PUT': response = requests.put(url, data=body.encode('utf8'), headers=headers, timeout=5) else: response = requests.get(url, headers=headers, timeout=5) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if response.status_code != 200 and response.status_code != 201 and response.status_code != 202: raise SendException("Got non-200 response [%d] from API" % response.status_code, event=event, start=start) Channel.success(channel, msg, WIRED, start, event=event)
def send(self, channel, msg, text): channel_access_token = channel.config.get(Channel.CONFIG_AUTH_TOKEN) data = json.dumps({ 'to': msg.urn_path, 'messages': [{ 'type': 'text', 'text': text }] }) start = time.time() headers = http_headers( extra={ 'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % channel_access_token }) send_url = 'https://api.line.me/v2/bot/message/push' event = HttpEvent('POST', send_url, data) try: response = requests.post(send_url, data=data, headers=headers) response.json() event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(six.text_type(e), event=event, start=start) if response.status_code not in [200, 201, 202]: # pragma: needs cover raise SendException("Got non-200 response [%d] from Line" % response.status_code, event=event, start=start) Channel.success(channel, msg, WIRED, start, event=event)
def get_existing_numbers(self, org): auth_id = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_ID, None) auth_token = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_TOKEN, None) headers = http_headers(extra={"Content-Type": "application/json"}) response = requests.get( "https://api.plivo.com/v1/Account/%s/Number/" % auth_id, headers=headers, auth=(auth_id, auth_token) ) account_numbers = [] if response.status_code == 200: data = response.json() for number_dict in data["objects"]: region = number_dict["region"] country_name = region.split(",")[-1].strip().title() country = pycountry.countries.get(name=country_name).alpha_2 if len(number_dict["number"]) <= 6: phone_number = number_dict["number"] else: parsed = phonenumbers.parse("+" + number_dict["number"], None) phone_number = phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.INTERNATIONAL) account_numbers.append(dict(number=phone_number, country=country)) return account_numbers
def send(self, channel, msg, text): encoding, text = Channel.determine_encoding(text, replace=True) # http://http1.javna.com/epicenter/gatewaysendG.asp?LoginName=xxxx&Password=xxxx&Tracking=1&Mobtyp=1&MessageRecipients=962796760057&MessageBody=hi&SenderName=Xxx params = { 'LoginName': channel.config[Channel.CONFIG_USERNAME], 'Password': channel.config[Channel.CONFIG_PASSWORD], 'Tracking': 1, 'Mobtyp': 1, 'MessageRecipients': msg.urn_path.lstrip('+'), 'MessageBody': text, 'SenderName': channel.address.lstrip('+') } # we are unicode if encoding == Encoding.UNICODE: params['Msgtyp'] = 10 if len(text) >= 70 else 9 elif len(text) > 160: params['Msgtyp'] = 5 url = 'http://http1.javna.com/epicenter/GatewaySendG.asp' event = HttpEvent('GET', url + '?' + urlencode(params)) start = time.time() try: response = requests.get(url, params=params, headers=http_headers(), timeout=15) event.status_code = response.status_code event.response_body = response.text except Exception as e: # pragma: no cover raise SendException(six.text_type(e), event=event, start=start) Channel.success(channel, msg, WIRED, start, event=event)
def claim_number(self, user, phone_number, country, role): auth_id = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_ID, None) auth_token = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_TOKEN, None) org = user.get_org() plivo_uuid = generate_uuid() callback_domain = org.get_brand_domain() app_name = "%s/%s" % (callback_domain.lower(), plivo_uuid) message_url = "https://" + callback_domain + "%s" % reverse("courier.pl", args=[plivo_uuid, "receive"]) answer_url = "https://" + settings.TEMBA_HOST+settings.MEDIA_URL+ "plivo_voice_unavailable.xml" headers = http_headers(extra={"Content-Type": "application/json"}) create_app_url = "https://api.plivo.com/v1/Account/%s/Application/" % auth_id response = requests.post( create_app_url, json=dict(app_name=app_name, answer_url=answer_url, message_url=message_url), headers=headers, auth=(auth_id, auth_token), ) if response.status_code in [201, 200, 202]: plivo_app_id = response.json()["app_id"] else: # pragma: no cover plivo_app_id = None plivo_config = { Channel.CONFIG_PLIVO_AUTH_ID: auth_id, Channel.CONFIG_PLIVO_AUTH_TOKEN: auth_token, Channel.CONFIG_PLIVO_APP_ID: plivo_app_id, Channel.CONFIG_CALLBACK_DOMAIN: org.get_brand_domain(), } plivo_number = phone_number.strip("+ ").replace(" ", "") response = requests.get( "https://api.plivo.com/v1/Account/%s/Number/%s/" % (auth_id, plivo_number), headers=headers, auth=(auth_id, auth_token), ) if response.status_code != 200: response = requests.post( "https://api.plivo.com/v1/Account/%s/PhoneNumber/%s/" % (auth_id, plivo_number), headers=headers, auth=(auth_id, auth_token), ) if response.status_code != 201: # pragma: no cover raise Exception( _("There was a problem claiming that number, please check the balance on your account.") ) response = requests.get( "https://api.plivo.com/v1/Account/%s/Number/%s/" % (auth_id, plivo_number), headers=headers, auth=(auth_id, auth_token), ) if response.status_code == 200: response = requests.post( "https://api.plivo.com/v1/Account/%s/Number/%s/" % (auth_id, plivo_number), json=dict(app_id=plivo_app_id), headers=headers, auth=(auth_id, auth_token), ) if response.status_code != 202: # pragma: no cover raise Exception(_("There was a problem updating that number, please try again.")) phone_number = "+" + plivo_number phone = phonenumbers.format_number( phonenumbers.parse(phone_number, None), phonenumbers.PhoneNumberFormat.NATIONAL ) channel = Channel.create( org, user, country, "PL", name=phone, address=phone_number, config=plivo_config, uuid=plivo_uuid ) analytics.track(user.username, "temba.channel_claim_plivo", dict(number=phone_number)) return channel
def call_webhook(run, webhook_url, ruleset, msg, action="POST", resthook=None, headers=None): from temba.api.models import WebHookEvent, WebHookResult from temba.flows.models import Flow 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) if not action: # pragma: needs cover action = "POST" if resthook: WebHookEvent.objects.create(org=org, data=post_data, action=action, resthook=resthook) status_code = -1 message = "None" body = None request = "" 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) s = requests.Session() # some hosts deny generic user agents, use Temba as our user agent if action == "GET": prepped = requests.Request("GET", webhook_url, headers=requests_headers).prepare() else: requests_headers["Content-type"] = "application/json" prepped = requests.Request("POST", webhook_url, data=json.dumps(post_data), headers=requests_headers).prepare() request = prepped_request_to_str(prepped) response = s.send(prepped, 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 not (200 <= status_code < 300): message = "Got non 200 response (%d) from webhook." % response.status_code raise ValueError("Got non 200 response (%d) from webhook." % response.status_code) except (requests.ReadTimeout, ValueError) as e: message = f"Error calling webhook: {str(e)}" except Exception as e: logger.error(f"Could not trigger flow webhook: {str(e)}", exc_info=True) message = "Error calling webhook: %s" % str(e) finally: # make sure our message isn't too long if message: message = message[:255] if body is None: body = message request_time = (time.time() - start) * 1000 contact = None if run: contact = run.contact result = WebHookResult.objects.create( contact=contact, url=webhook_url, status_code=status_code, response=body, request=request, request_time=request_time, org=run.org, ) return result
def claim_number(self, user, phone_number, country, role): auth_id = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_ID, None) auth_token = self.request.session.get(Channel.CONFIG_PLIVO_AUTH_TOKEN, None) org = user.get_org() plivo_uuid = generate_uuid() callback_domain = org.get_brand_domain() app_name = "%s/%s" % (callback_domain.lower(), plivo_uuid) message_url = f"https://{callback_domain}{reverse('courier.pl', args=[plivo_uuid, 'receive'])}" answer_url = f"{settings.STORAGE_URL}/plivo_voice_unavailable.xml" headers = http_headers(extra={"Content-Type": "application/json"}) create_app_url = "https://api.plivo.com/v1/Account/%s/Application/" % auth_id response = requests.post( create_app_url, json=dict(app_name=app_name, answer_url=answer_url, message_url=message_url), headers=headers, auth=(auth_id, auth_token), ) if response.status_code in [201, 200, 202]: plivo_app_id = response.json()["app_id"] else: # pragma: no cover plivo_app_id = None plivo_config = { Channel.CONFIG_PLIVO_AUTH_ID: auth_id, Channel.CONFIG_PLIVO_AUTH_TOKEN: auth_token, Channel.CONFIG_PLIVO_APP_ID: plivo_app_id, Channel.CONFIG_CALLBACK_DOMAIN: org.get_brand_domain(), } plivo_number = phone_number.strip("+ ").replace(" ", "") response = requests.get( "https://api.plivo.com/v1/Account/%s/Number/%s/" % (auth_id, plivo_number), headers=headers, auth=(auth_id, auth_token), ) if response.status_code != 200: response = requests.post( "https://api.plivo.com/v1/Account/%s/PhoneNumber/%s/" % (auth_id, plivo_number), headers=headers, auth=(auth_id, auth_token), ) if response.status_code != 201: # pragma: no cover raise Exception( _("There was a problem claiming that number, please check the balance on your account.") ) response = requests.get( "https://api.plivo.com/v1/Account/%s/Number/%s/" % (auth_id, plivo_number), headers=headers, auth=(auth_id, auth_token), ) if response.status_code == 200: response = requests.post( "https://api.plivo.com/v1/Account/%s/Number/%s/" % (auth_id, plivo_number), json=dict(app_id=plivo_app_id), headers=headers, auth=(auth_id, auth_token), ) if response.status_code != 202: # pragma: no cover raise Exception(_("There was a problem updating that number, please try again.")) phone_number = "+" + plivo_number phone = phonenumbers.format_number( phonenumbers.parse(phone_number, None), phonenumbers.PhoneNumberFormat.NATIONAL ) channel = Channel.create( org, user, country, "PL", name=phone, address=phone_number, config=plivo_config, uuid=plivo_uuid ) return channel
def _request(self, url, params=None, access_token=None): headers = http_headers(extra={"Authorization": "Bearer " + access_token} if access_token else {}) response = requests.post(url, data=params, headers=headers, timeout=15) return response
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 deliver(self): from .v1.serializers import MsgCreateSerializer start = time.time() # create our post parameters post_data = json.loads(self.data) post_data['event'] = self.event post_data['relayer'] = self.channel.pk if self.channel else '' post_data['channel'] = self.channel.pk if self.channel else '' post_data[ 'relayer_phone'] = self.channel.address if self.channel else '' # look up the endpoint for this channel result = dict(url=self.org.get_webhook_url(), data=urlencode(post_data, doseq=True)) if not self.org.get_webhook_url(): # pragma: no cover result['status_code'] = 0 result[ 'message'] = "No webhook registered for this org, ignoring event" self.status = self.STATUS_FAILED self.next_attempt = None return result # get our org user user = self.org.get_user() # no user? we shouldn't be doing webhooks shtuff if not user: result['status_code'] = 0 result['message'] = "No active user for this org, ignoring event" self.status = self.STATUS_FAILED self.next_attempt = None return result # make the request try: if not settings.SEND_WEBHOOKS: raise Exception( "!! Skipping WebHook send, SEND_WEBHOOKS set to False") headers = http_headers(extra=self.org.get_webhook_headers()) s = requests.Session() prepped = requests.Request('POST', self.org.get_webhook_url(), data=post_data, headers=headers).prepare() result['url'] = prepped.url result['request'] = prepped_request_to_str(prepped) r = s.send(prepped, timeout=5) result['status_code'] = r.status_code result['body'] = r.text.strip() r.raise_for_status() # any 200 code is ok by us self.status = self.STATUS_COMPLETE result['request_time'] = (time.time() - start) * 1000 result['message'] = "Event delivered successfully." # read our body if we have one if result['body']: try: data = r.json() serializer = MsgCreateSerializer(data=data, user=user, org=self.org) if serializer.is_valid(): result['serializer'] = serializer result[ 'message'] = "Response body contains message which will be sent" else: errors = serializer.errors result['message'] = "Event delivered successfully, ignoring response body, wrong format: %s" % \ ",".join("%s: %s" % (_, ",".join(errors[_])) for _ in errors.keys()) except ValueError as e: # we were unable to make anything of the body, that's ok though because # get a 200, so just save our error for posterity result[ 'message'] = "Event delivered successfully, ignoring response body, not JSON: %s" % six.text_type( e) except Exception as e: # we had an error, log it self.status = self.STATUS_ERRORED result['request_time'] = time.time() - start result[ 'message'] = "Error when delivering event - %s" % six.text_type( e) # if we had an error of some kind, schedule a retry for five minutes from now self.try_count += 1 if self.status == self.STATUS_ERRORED: if self.try_count < 3: self.next_attempt = timezone.now() + timedelta(minutes=5) else: self.next_attempt = None self.status = 'F' else: self.next_attempt = None 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 send(self, channel, msg, text): connection = None # if the channel config has specified and override hostname use that, otherwise use settings callback_domain = channel.config.get(Channel.CONFIG_RP_HOSTNAME_OVERRIDE, None) if not callback_domain: callback_domain = channel.callback_domain # the event url Junebug will relay events to event_url = "http://%s%s" % ( callback_domain, reverse("handlers.junebug_handler", args=["event", channel.uuid]), ) is_ussd = Channel.get_type_from_code(channel.channel_type).category == ChannelType.Category.USSD # build our payload payload = {"event_url": event_url, "content": text} secret = channel.config.get(Channel.CONFIG_SECRET) if secret is not None: payload["event_auth_token"] = secret connection = USSDSession.objects.get_with_status_only(msg.connection_id) # make sure USSD responses are only valid for a short window response_expiration = timezone.now() - timedelta(seconds=180) external_id = None if msg.response_to_id and msg.created_on > response_expiration: external_id = Msg.objects.values_list("external_id", flat=True).filter(pk=msg.response_to_id).first() # NOTE: Only one of `to` or `reply_to` may be specified, use external_id if we have it. if external_id: payload["reply_to"] = external_id else: payload["to"] = msg.urn_path payload["channel_data"] = {"continue_session": connection and not connection.should_end or False} log_url = channel.config[Channel.CONFIG_SEND_URL] start = time.time() event = HttpEvent("POST", log_url, json.dumps(payload)) headers = http_headers(extra={"Content-Type": "application/json"}) try: response = requests.post( channel.config[Channel.CONFIG_SEND_URL], verify=True, json=payload, timeout=15, headers=headers, auth=(channel.config[Channel.CONFIG_USERNAME], channel.config[Channel.CONFIG_PASSWORD]), ) event.status_code = response.status_code event.response_body = response.text except Exception as e: raise SendException(str(e), event=event, start=start) if not (200 <= response.status_code < 300): raise SendException( "Received a non 200 response %d from Junebug" % response.status_code, event=event, start=start ) data = response.json() if is_ussd and connection and connection.should_end: connection.close() try: message_id = data["result"]["message_id"] Channel.success(channel, msg, WIRED, start, event=event, external_id=message_id) except KeyError as e: raise SendException( "Unable to read external message_id: %r" % (e,), event=HttpEvent( "POST", log_url, request_body=json.dumps(json.dumps(payload)), response_body=json.dumps(data) ), start=start, )
def deliver(self): from .v1.serializers import MsgCreateSerializer start = time.time() # create our post parameters post_data = self.data post_data["event"] = self.event post_data["relayer"] = self.channel.pk if self.channel else "" post_data["channel"] = self.channel.pk if self.channel else "" post_data["relayer_phone"] = self.channel.address if self.channel else "" # look up the endpoint for this channel result = dict(url=self.org.get_webhook_url(), data=urlencode(post_data, doseq=True)) if not self.org.get_webhook_url(): # pragma: no cover result["status_code"] = 0 result["message"] = "No webhook registered for this org, ignoring event" self.status = self.STATUS_FAILED self.next_attempt = None return result # get our org user user = self.org.get_user() # no user? we shouldn't be doing webhooks shtuff if not user: result["status_code"] = 0 result["message"] = "No active user for this org, ignoring event" self.status = self.STATUS_FAILED self.next_attempt = None return result # make the request try: if not settings.SEND_WEBHOOKS: # pragma: no cover raise Exception("!! Skipping WebHook send, SEND_WEBHOOKS set to False") headers = http_headers(extra=self.org.get_webhook_headers()) s = requests.Session() prepped = requests.Request("POST", self.org.get_webhook_url(), data=post_data, headers=headers).prepare() result["url"] = prepped.url result["request"] = prepped_request_to_str(prepped) r = s.send(prepped, timeout=5) result["status_code"] = r.status_code result["body"] = r.text.strip() r.raise_for_status() # any 200 code is ok by us self.status = self.STATUS_COMPLETE result["request_time"] = (time.time() - start) * 1000 result["message"] = "Event delivered successfully." # read our body if we have one if result["body"]: try: data = r.json() serializer = MsgCreateSerializer(data=data, user=user, org=self.org) if serializer.is_valid(): result["serializer"] = serializer result["message"] = "Response body contains message which will be sent" else: errors = serializer.errors result["message"] = ( "Event delivered successfully, ignoring response body, wrong format: %s" % ",".join("%s: %s" % (_, ",".join(errors[_])) for _ in errors.keys()) ) except ValueError as e: # we were unable to make anything of the body, that's ok though because # get a 200, so just save our error for posterity result["message"] = "Event delivered successfully, ignoring response body, not JSON: %s" % str(e) except Exception as e: # we had an error, log it self.status = self.STATUS_ERRORED result["request_time"] = time.time() - start result["message"] = "Error when delivering event - %s" % str(e) # if we had an error of some kind, schedule a retry for five minutes from now self.try_count += 1 if self.status == self.STATUS_ERRORED: if self.try_count < 3: self.next_attempt = timezone.now() + timedelta(minutes=5) else: self.next_attempt = None self.status = "F" else: self.next_attempt = None return result