def send(self, channel, msg, text): # url used for logs and exceptions url = 'https://api.plivo.com/v1/Account/%s/Message/' % channel.config[Channel.CONFIG_PLIVO_AUTH_ID] client = plivo.RestAPI(channel.config[Channel.CONFIG_PLIVO_AUTH_ID], channel.config[Channel.CONFIG_PLIVO_AUTH_TOKEN]) status_url = "https://" + settings.TEMBA_HOST + "%s" % 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)) start = time.time() try: # TODO: Grab real request and response here plivo_response_status, plivo_response = client.send_message(params=payload) event.status_code = plivo_response_status event.response_body = plivo_response except Exception as e: # pragma: no cover raise SendException(six.text_type(e), event=event, start=start) if plivo_response_status != 200 and plivo_response_status != 201 and plivo_response_status != 202: raise SendException("Got non-200 response [%d] from API" % plivo_response_status, event=event, start=start) external_id = plivo_response['message_uuid'][0] Channel.success(channel, msg, WIRED, start, event=event, external_id=external_id)
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 refresh_access_token(self, channel_id): r = get_redis_connection() lock_name = self.TOKEN_REFRESH_LOCK % self.channel_uuid if not r.get(lock_name): with r.lock(lock_name, timeout=30): key = self.TOKEN_STORE_KEY % self.channel_uuid post_data = dict(grant_type="client_credentials", client_id=self.app_id, client_secret=self.app_secret) url = self.TOKEN_URL event = HttpEvent("POST", url, json.dumps(post_data)) start = time.time() response = self._request(url, post_data, access_token=None) event.status_code = response.status_code if response.status_code != 200: event.response_body = response.content ChannelLog.log_channel_request( channel_id, "Got non-200 response from %s" % self.API_NAME, event, start, True ) return response_json = response.json() event.response_body = json.dumps(response_json) ChannelLog.log_channel_request( channel_id, "Successfully fetched access token from %s" % self.API_NAME, event, start ) access_token = response_json["access_token"] expires = response_json.get("expires_in", 7200) r.set(key, access_token, ex=int(expires)) return access_token
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 request(self, method, uri, **kwargs): data = kwargs.get("data") if data is not None: udata = {} for k, v in data.items(): key = k.encode("utf-8") if isinstance(v, (list, tuple, set)): udata[key] = [encode_atom(x) for x in v] elif isinstance(v, (int, bytes, str)): udata[key] = encode_atom(v) else: raise ValueError("data should be an integer, " "binary, or string, or sequence ") data = urlencode(udata, doseq=True) del kwargs["auth"] event = HttpEvent(method, uri, data) if "/messages" in uri.lower() or "/calls" in uri.lower(): self.events.append(event) resp = super().request(method, uri, auth=self.auth, **kwargs) event.url = uri event.status_code = resp.status_code event.response_body = force_text(resp.content) return resp
def request(self, method, uri, **kwargs): """ Send an HTTP request to the resource. :raises: a :exc:`~twilio.TwilioRestException` """ if 'timeout' not in kwargs and self.timeout is not UNSET_TIMEOUT: kwargs['timeout'] = self.timeout data = kwargs.get('data') if data is not None: udata = {} for k, v in six.iteritems(data): key = k.encode('utf-8') if isinstance(v, (list, tuple, set)): udata[key] = [encode_atom(x) for x in v] elif isinstance(v, (six.integer_types, six.binary_type, six.string_types)): udata[key] = encode_atom(v) else: raise ValueError('data should be an integer, ' 'binary, or string, or sequence ') data = urlencode(udata, doseq=True) event = HttpEvent(method, uri, data) self.events.append(event) resp = make_twilio_request(method, uri, auth=self.auth, **kwargs) event.url = resp.url event.status_code = resp.status_code event.response_body = six.text_type(resp.content) if method == "DELETE": return resp, {} else: return resp, json.loads(resp.content)
def get_user_detail(self, open_id, channel_id): access_token = self.get_access_token() url = 'https://channels.jiochat.com/user/info.action?' payload = dict(openid=open_id) event = HttpEvent('GET', url, json.dumps(payload)) start = time.time() response = self._request(url, 'GET', payload, access_token) event.status_code = response.status_code if response.status_code != 200: event.response_body = response.content ChannelLog.log_channel_request( channel_id, "Got non-200 response from Jiochat", event, start, True) return dict() data = response.json() event.response_body = json.dumps(data) ChannelLog.log_channel_request( channel_id, "Successfully fetched user detail from Jiochat", event, start) return data
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 refresh_access_token(self, channel_id): r = get_redis_connection() lock_name = JIOCHAT_ACCESS_TOKEN_REFRESH_LOCK % self.channel_uuid if not r.get(lock_name): with r.lock(lock_name, timeout=30): key = JIOCHAT_ACCESS_TOKEN_KEY % self.channel_uuid post_data = dict(grant_type="client_credentials", client_id=self.app_id, client_secret=self.app_secret) url = "https://channels.jiochat.com/auth/token.action" event = HttpEvent("POST", url, json.dumps(post_data)) start = time.time() response = self._request(url, post_data, access_token=None) event.status_code = response.status_code if response.status_code != 200: event.response_body = response.content ChannelLog.log_channel_request(channel_id, "Got non-200 response from Jiochat", event, start, True) return response_json = response.json() event.response_body = json.dumps(response_json) ChannelLog.log_channel_request( channel_id, "Successfully fetched access token from Jiochat", event, start ) access_token = response_json["access_token"] r.set(key, access_token, ex=7200) return access_token
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): # 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 request_media(self, media_id, channel_id): access_token = self.get_access_token() url = 'https://channels.jiochat.com/media/download.action' payload = dict(media_id=media_id) event = HttpEvent('GET', url, json.dumps(payload)) start = time.time() response = None attempts = 0 while attempts < 4: response = self._request(url, 'GET', payload, access_token) # If we fail sleep for a bit then try again up to 4 times if response.status_code == 200: break else: attempts += 1 time.sleep(.250) if response: event.status_code = response.status_code if not event.status_code or event.status_code != 200: ChannelLog.log_channel_request(channel_id, "Failed to get media from Jiochat", event, start, True) else: ChannelLog.log_channel_request(channel_id, "Successfully fetched media from Jiochat", event, start) return response
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 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 = dict() params['LoginName'] = channel.config[Channel.CONFIG_USERNAME] params['Password'] = channel.config[Channel.CONFIG_PASSWORD] params['Tracking'] = 1 params['Mobtyp'] = 1 params['MessageRecipients'] = msg.urn_path.lstrip('+') params['MessageBody'] = text params['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=TEMBA_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 send_message_via_nexmo(self, from_number, to_number, text): from temba.channels.models import SendException params = dict(api_key=self.api_key, api_secret=self.api_secret) params['from'] = from_number.strip('+') params['to'] = to_number.strip('+') params['text'] = text params['status-report-req'] = 1 # if this isn't going to work as plaintext, send as unicode instead if not is_gsm7(text): params['type'] = 'unicode' log_params = params.copy() log_params['api_secret'] = 'x' * len(log_params['api_secret']) log_url = NexmoClient.SEND_URL + '?' + urlencode(log_params) event = HttpEvent('GET', log_url) try: response = requests.get(NexmoClient.SEND_URL, params=params) event.status_code = response.status_code event.response_body = response.text response_json = response.json() messages = response_json.get('messages', []) except Exception: raise SendException(u"Failed sending message: %s" % response.text, event=event) if not messages or int(messages[0]['status']) != 0: raise SendException(u"Failed sending message, received error status [%s]" % messages[0]['status'], event=event) else: return messages[0]['message-id'], event
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): 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 = dict(Accept='application/json', apikey=channel.config['api_key']) headers.update(TEMBA_HEADERS) 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): auth_token = channel.config['auth_token'] send_url = 'https://api.telegram.org/bot%s/sendMessage' % auth_token post_body = {'chat_id': msg.urn_path, 'text': text} metadata = msg.metadata if hasattr(msg, 'metadata') else {} quick_replies = metadata.get('quick_replies', []) formatted_replies = json.dumps( dict(resize_keyboard=True, one_time_keyboard=True, keyboard=[[dict(text=item[:self.quick_reply_text_size])] for item in quick_replies])) if quick_replies: post_body['reply_markup'] = formatted_replies start = time.time() # for now we only support sending one attachment per message but this could change in future attachments = Attachment.parse_all(msg.attachments) attachment = attachments[0] if attachments else None if attachment: category = attachment.content_type.split('/')[0] if category == 'image': send_url = 'https://api.telegram.org/bot%s/sendPhoto' % auth_token post_body['photo'] = attachment.url post_body['caption'] = text del post_body['text'] elif category == 'video': send_url = 'https://api.telegram.org/bot%s/sendVideo' % auth_token post_body['video'] = attachment.url post_body['caption'] = text del post_body['text'] elif category == 'audio': send_url = 'https://api.telegram.org/bot%s/sendAudio' % auth_token post_body['audio'] = attachment.url post_body['caption'] = text del post_body['text'] event = HttpEvent('POST', send_url, urlencode(post_body)) try: response = requests.post(send_url, post_body) event.status_code = response.status_code event.response_body = response.text external_id = response.json()['result']['message_id'] except Exception as e: raise SendException(str(e), event=event, start=start) 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): 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): # 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): 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 send(self, channel, msg, text): # determine our encoding encoding, text = Channel.determine_encoding(text, replace=True) # if this looks like unicode, ask m3tech to send as unicode if encoding == Encoding.UNICODE: sms_type = '7' else: sms_type = '0' url = 'https://secure.m3techservice.com/GenericServiceRestAPI/api/SendSMS' payload = {'AuthKey': 'm3-Tech', 'UserId': channel.config[Channel.CONFIG_USERNAME], 'Password': channel.config[Channel.CONFIG_PASSWORD], 'MobileNo': msg.urn_path.lstrip('+'), 'MsgId': msg.id, 'SMS': text, 'MsgHeader': channel.address.lstrip('+'), 'SMSType': sms_type, 'HandsetPort': '0', 'SMSChannel': '0', 'Telco': '0'} event = HttpEvent('GET', url + "?" + urlencode(payload)) start = time.time() try: response = requests.get(url, params=payload, headers=TEMBA_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) # our response is JSON and should contain a 0 as a status code: # [{"Response":"0"}] try: response_code = json.loads(response.text)[0]["Response"] except Exception as e: response_code = str(e) # <Response>0</Response> if response_code != "0": raise SendException("Received non-zero status from API: %s" % str(response_code), event=event, start=start) Channel.success(channel, msg, WIRED, start, event=event)
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_whatsapp(self, channel_struct, msg, payload, attachments=None): url = ('%s/messages/' % (self.wassup_url(), )) headers = self.api_request_headers(channel_struct) event = HttpEvent('POST', url, json.dumps(payload)) start = time.time() # Grab the first attachment if it exists attachments = Attachment.parse_all(msg.attachments) attachment = attachments[0] if attachments else None try: if attachment: files = self.fetch_attachment(attachment) data = payload else: headers.update({'Content-Type': 'application/json'}) data = json.dumps(payload) files = {} response = requests.post(url, data=data, files=files, headers=headers) response.raise_for_status() event.status_code = response.status_code event.response_body = response.text except (requests.RequestException, ) as e: raise SendException( 'error: %s, request: %r, response: %r' % (six.text_type(e), e.request.body, e.response.content), event=event, start=start) data = response.json() try: message_id = data['uuid'] Channel.success(channel_struct, 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', url, request_body=json.dumps(json.dumps(payload)), response_body=json.dumps(data)), start=start)
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 = {'Accept': 'application/json'} headers.update(TEMBA_HEADERS) 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): # 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 download_media(self, call, media_url): """ Fetches the recording and stores it with the provided recording_id :param media_url: the url where the media lives :return: the url for our downloaded media with full content type prefix """ attempts = 0 response = None while attempts < 4: response = self.download_recording(media_url) # in some cases Twilio isn't ready for us to fetch the recording URL yet, if we get a 404 # sleep for a bit then try again up to 4 times if response.status_code == 200: break else: attempts += 1 time.sleep(0.250) content_type, downloaded = self.org.save_response_media(response) if content_type: # log that we downloaded it to our own url request = response.request event = HttpEvent(request.method, request.url, request.body, response.status_code, downloaded) ChannelLog.log_ivr_interaction(call, "Downloaded media", event) return "%s:%s" % (content_type, downloaded) return None
def start_call(self, call, to, from_, status_callback): if not settings.SEND_CALLS: raise ValueError("SEND_CALLS set to False, skipping call start") url = "https://%s%s" % (self.org.get_brand_domain(), reverse("ivr.ivrcall_handle", args=[call.pk])) params = dict() params["answer_url"] = [url] params["answer_method"] = "POST" params["to"] = [dict(type="phone", number=to.strip("+"))] params["from"] = dict(type="phone", number=from_.strip("+")) params["event_url"] = ["%s?has_event=1" % url] params["event_method"] = "POST" try: response = self.create_call(params=params) call_uuid = response.get("uuid", None) call.external_id = str(call_uuid) # the call was successfully sent to the IVR provider call.status = IVRCall.WIRED call.save() for event in self.events: ChannelLog.log_ivr_interaction(call, "Started call", event) except Exception as e: event = HttpEvent("POST", "https://api.nexmo.com/v1/calls", json.dumps(params), response_body=str(e)) ChannelLog.log_ivr_interaction(call, "Call start failed", event, is_error=True) call.status = IVRCall.FAILED call.save() raise IVRException(_("Nexmo call failed, with error %s") % str(e))
def start_call(self, call, to, from_, status_callback): if not settings.SEND_CALLS: raise ValueError("SEND_CALLS set to False, skipping call start") params = dict(to=to, from_=call.channel.address, url=status_callback, status_callback=status_callback) try: twilio_call = self.api.calls.create(**params) call.external_id = str(twilio_call.sid) # the call was successfully sent to the IVR provider call.status = IVRCall.WIRED call.save() for event in self.events: ChannelLog.log_ivr_interaction(call, "Started call", event) except TwilioRestException as twilio_error: message = "Twilio Error: %s" % twilio_error.msg if twilio_error.code == 20003: message = _("Could not authenticate with your Twilio account. Check your token and try again.") event = HttpEvent("POST", "https://api.nexmo.com/v1/calls", json.dumps(params), response_body=str(message)) ChannelLog.log_ivr_interaction(call, "Call start failed", event, is_error=True) call.status = IVRCall.FAILED call.save() raise IVRException(message)
def refresh_access_token(self, channel_id): r = get_redis_connection() lock_name = self.TOKEN_REFRESH_LOCK % self.channel_uuid if not r.get(lock_name): with r.lock(lock_name, timeout=30): key = self.TOKEN_STORE_KEY % self.channel_uuid data = dict(grant_type="client_credential", appid=self.app_id, secret=self.app_secret) url = self.TOKEN_URL event = HttpEvent("GET", url + "?" + urlencode(data)) start = time.time() response = requests.get(url, params=data, timeout=15) event.status_code = response.status_code if response.status_code != 200: event.response_body = response.content ChannelLog.log_channel_request( channel_id, "Got non-200 response from %s" % self.API_NAME, event, start, True ) return response_json = response.json() has_error = False if response_json.get("errcode", -1) != 0: has_error = True event.response_body = json.dumps(response_json) ChannelLog.log_channel_request( channel_id, "Successfully fetched access token from %s" % self.API_NAME, event, start, has_error ) access_token = response_json.get("access_token", "") expires = response_json.get("expires_in", 7200) if access_token: r.set(key, access_token, ex=int(expires)) return access_token
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 _request(self, url, method="GET", params=None, api_call=None): """Internal request method""" method = method.lower() params = params or {} func = getattr(self.client, method) params, files = (params, None) if "event" in params else _transparent_params(params) requests_args = {} for k, v in self.client_args.items(): # Maybe this should be set as a class variable and only done once? if k in ("timeout", "allow_redirects", "stream", "verify"): requests_args[k] = v if method == "get": requests_args["params"] = params else: requests_args.update({"data": json.dumps(params) if "event" in params else params, "files": files}) try: if method == "get": event = HttpEvent(method, url + "?" + urlencode(params)) else: event = HttpEvent(method, url, urlencode(params)) self.events.append(event) response = func(url, **requests_args) event.status_code = response.status_code event.response_body = response.text except requests.RequestException as e: raise TwythonError(str(e)) content = response.content.decode("utf-8") # create stash for last function intel self._last_call = { "api_call": api_call, "api_error": None, "cookies": response.cookies, "headers": response.headers, "status_code": response.status_code, "url": response.url, "content": content, } # Wrap the json loads in a try, and defer an error # Twitter will return invalid json with an error code in the headers json_error = False if content: try: try: # try to get json content = content.json() except AttributeError: # if unicode detected content = json.loads(content) except ValueError: json_error = True content = {} if response.status_code > 304: # If there is no error message, use a default. errors = content.get("errors", [{"message": "An error occurred processing your request."}]) if errors and isinstance(errors, list): error_message = errors[0]["message"] else: error_message = errors # pragma: no cover self._last_call["api_error"] = error_message ExceptionType = TwythonError if response.status_code == 429: # Twitter API 1.1, always return 429 when rate limit is exceeded ExceptionType = TwythonRateLimitError # pragma: no cover elif response.status_code == 401 or "Bad Authentication data" in error_message: # Twitter API 1.1, returns a 401 Unauthorized or # a 400 "Bad Authentication data" for invalid/expired app keys/user tokens ExceptionType = TwythonAuthError raise ExceptionType( error_message, error_code=response.status_code, retry_after=response.headers.get("retry-after") ) # if we have a json error here, then it's not an official Twitter API error if json_error and response.status_code not in (200, 201, 202): # pragma: no cover raise TwythonError("Response was not valid JSON, unable to decode.") return content