def process_incoming_message(*args, **kwargs): try: from corehq.messaging.smsbackends.telerivet.views import TELERIVET_INBOUND_FIELD_MAP fields = {a: kwargs[a] for (a, b) in TELERIVET_INBOUND_FIELD_MAP} log = IncomingRequest(**fields) log.save() except Exception as e: notify_exception(None, "Could not save Telerivet log entry") pass backend = SQLTelerivetBackend.by_webhook_secret(kwargs["secret"]) if backend is None: # Ignore the message if the webhook secret is not recognized return if kwargs["from_number_e164"]: from_number = strip_plus(kwargs["from_number_e164"]) else: from_number = strip_plus(kwargs["from_number"]) if kwargs["event"] == EVENT_INCOMING: if kwargs["message_type"] == MESSAGE_TYPE_SMS: domain_scope = backend.domain if not backend.is_global else None incoming_sms(from_number, kwargs["content"], SQLTelerivetBackend.get_api_id(), domain_scope=domain_scope, backend_id=backend.couch_id) elif kwargs["message_type"] == MESSAGE_TYPE_CALL: incoming_ivr(from_number, "TELERIVET-%s" % kwargs["message_id"], None)
def _update(self, bundle): should_save = False for key, value in bundle.data.items(): if getattr(bundle.obj, key, None) != value: if key == 'phone_numbers': bundle.obj.phone_numbers = [] for idx, phone_number in enumerate(bundle.data.get('phone_numbers', [])): bundle.obj.add_phone_number(strip_plus(phone_number)) if idx == 0: bundle.obj.set_default_phone_number(strip_plus(phone_number)) should_save = True elif key == 'groups': bundle.obj.set_groups(bundle.data.get("groups", [])) should_save = True elif key in ['email', 'username']: setattr(bundle.obj, key, value.lower()) should_save = True elif key == 'password': domain = Domain.get_by_name(bundle.obj.domain) if domain.strong_mobile_passwords: try: clean_password(bundle.data.get("password")) except ValidationError as e: if not hasattr(bundle.obj, 'errors'): bundle.obj.errors = [] bundle.obj.errors.append(e.message) return False bundle.obj.set_password(bundle.data.get("password")) should_save = True else: setattr(bundle.obj, key, value) should_save = True return should_save
def process_incoming_message(*args, **kwargs): try: from corehq.apps.telerivet.views import TELERIVET_INBOUND_FIELD_MAP fields = {a: kwargs[a] for (a, b) in TELERIVET_INBOUND_FIELD_MAP} log = IncomingRequest(**fields) log.save() except Exception as e: notify_exception(None, "Could not save Telerivet log entry") pass backend = TelerivetBackend.by_webhook_secret(kwargs["secret"]) if backend is None: # Ignore the message if the webhook secret is not recognized return if kwargs["from_number_e164"]: from_number = strip_plus(kwargs["from_number_e164"]) else: from_number = strip_plus(kwargs["from_number"]) if kwargs["event"] == EVENT_INCOMING: if kwargs["message_type"] == MESSAGE_TYPE_SMS: incoming_sms(from_number, kwargs["content"], TelerivetBackend.get_api_id()) elif kwargs["message_type"] == MESSAGE_TYPE_CALL: incoming_ivr(from_number, None, "TELERIVET-%s" % kwargs["message_id"], None)
def run_script(self, script): commands = self.parse_script(script) for command in commands: phone_number = command['phone_number'] v = PhoneNumber.by_phone(phone_number) if command['direction'] == '>': incoming(phone_number, command['text'], v.backend_id) else: msg = self.get_last_outbound_sms(v.owner_doc_type, v.owner_id) self.assertEqual(msg.text, unicode(command['text'])) self.assertEqual(strip_plus(msg.phone_number), strip_plus(phone_number)) msg.delete()
def run_script(self, script): commands = self.parse_script(script) for command in commands: phone_number = command['phone_number'] v = PhoneNumber.get_two_way_number(phone_number) if command['direction'] == '>': incoming(phone_number, command['text'], v.backend_id) else: msg = self.get_last_outbound_sms(v.owner_doc_type, v.owner_id) self.assertEqual(msg.text, unicode(command['text'])) self.assertEqual(strip_plus(msg.phone_number), strip_plus(phone_number)) msg.delete()
def _update(self, bundle): should_save = False for key, value in bundle.data.items(): if key == 'phone_numbers' and getattr(bundle.obj, key, None) != value: bundle.obj.phone_numbers = [] for idx, phone_number in enumerate(bundle.data.get('phone_numbers', [])): bundle.obj.add_phone_number(strip_plus(phone_number)) if idx == 0: bundle.obj.set_default_phone_number(strip_plus(phone_number)) should_save = True elif getattr(bundle.obj, key, None) != value: setattr(bundle.obj, key, value) should_save = True return should_save
def create_session_object(cls, domain, contact, phone_number, app, form, expire_after=MAX_SESSION_LENGTH, reminder_intervals=None, submit_partially_completed_forms=False, include_case_updates_in_partial_submissions=False): now = utcnow() session = cls( couch_id=uuid.uuid4().hex, connection_id=contact.get_id, form_xmlns=form.xmlns, start_time=now, modified_time=now, completed=False, domain=domain, user_id=contact.get_id, app_id=app.get_id, session_type=XFORMS_SESSION_SMS, phone_number=strip_plus(phone_number), expire_after=expire_after, session_is_open=True, reminder_intervals=reminder_intervals or [], current_reminder_num=0, submit_partially_completed_forms=submit_partially_completed_forms, include_case_updates_in_partial_submissions=include_case_updates_in_partial_submissions, ) session.set_current_action_due_timestamp() return session
def initiate_outbound_call(self, call, logged_subevent, ivr_data=None): """ Same expected return value as corehq.apps.ivr.api.initiate_outbound_call """ phone_number = strip_plus(call.phone_number) if phone_number.startswith("91"): phone_number = "0%s" % phone_number[2:] else: log_error(MessagingEvent.ERROR_UNSUPPORTED_COUNTRY, call, logged_subevent) return True response = self.invoke_kookoo_outbound_api(phone_number) status, message = self.get_status_and_message(response) do_not_retry = False if status == "queued": call.error = False call.gateway_session_id = "KOOKOO-%s" % message elif status == "error": call.error = True call.error_message = message if message.strip().upper() in ["CALLS WILL NOT BE MADE BETWEEN 9PM TO 9AM.", "PHONE NUMBER IN DND LIST"]: # These are error messages that come from KooKoo and # are indicative of non-recoverable errors, so we # wouldn't benefit from retrying the call. do_not_retry = True logged_subevent.error(MessagingEvent.ERROR_GATEWAY_ERROR) else: log_error(MessagingEvent.ERROR_GATEWAY_ERROR, call, logged_subevent) return not call.error or do_not_retry
def send(self, msg, *args, **kwargs): if self.additional_params is not None: params = self.additional_params.copy() else: params = {} phone_number = msg.phone_number if self.include_plus: phone_number = clean_phone_number(phone_number) else: phone_number = strip_plus(phone_number) try: text = msg.text.encode("iso-8859-1") except UnicodeEncodeError: text = msg.text.encode("utf-8") params[self.message_param] = text params[self.number_param] = phone_number url_params = urlencode(params) try: if self.method == "GET": response = urlopen("%s?%s" % (self.url, url_params), timeout=settings.SMS_GATEWAY_TIMEOUT).read() else: response = urlopen(self.url, url_params, timeout=settings.SMS_GATEWAY_TIMEOUT).read() except Exception as e: msg = "Error sending message from backend: '{}'\n\n{}".format(self.name, str(e)) raise BackendProcessingException(msg), None, sys.exc_info()[2]
def send(self, msg, delay=True, *args, **kwargs): phone_number = strip_plus(msg.phone_number) if not phone_number.startswith("63"): raise MegamobileException( "Only Filipino phone numbers are supported") phone_number = phone_number[2:] text = msg.text.encode("utf-8") pid = None if msg.in_reply_to: original_msg = SMSLog.get(msg.in_reply_to) pid = getattr(original_msg, "megamobile_pid", None) pid = pid or DEFAULT_PID setattr(msg, "megamobile_pid", pid) msg.save() params = urlencode({ "pid": pid, "cel": phone_number, "msg": text, "src": self.source_identifier, }) api_account_name = quote(self.api_account_name) url = "http://api.mymegamobile.com/%s?%s" % (api_account_name, params) response = urlopen(url, timeout=settings.SMS_GATEWAY_TIMEOUT).read()
def send(self, msg, *args, **kwargs): config = self.config if config.additional_params: params = config.additional_params.copy() else: params = {} phone_number = msg.phone_number if config.include_plus: phone_number = clean_phone_number(phone_number) else: phone_number = strip_plus(phone_number) params[config.message_param] = self._encode_http_message(msg.text) params[config.number_param] = phone_number url_params = urlencode(params) try: unverified = ssl._create_unverified_context() if config.method == "GET": urlopen( "%s?%s" % (config.url, url_params), context=unverified, timeout=settings.SMS_GATEWAY_TIMEOUT, ).read() else: urlopen( config.url, url_params, context=unverified, timeout=settings.SMS_GATEWAY_TIMEOUT, ).read() except Exception as e: msg = "Error sending message from backend: '{}'\n\n{}".format(self.pk, str(e)) six.reraise(BackendProcessingException, BackendProcessingException(msg), sys.exc_info()[2])
def validate_row(row, domain, data_cols): """pre-validate the information in a particular import row: valid location, reporting user, and data formats """ # identify location loc_code = row.get('outlet_code') or row.get('site_code') row['loc'] = get_supply_point(domain, loc_code)['case'] if row['loc'] is None: set_error(row, 'ERROR location code is invalid') return # identify user phone = row.get('phone') owner = None if phone: vn = VerifiedNumber.by_phone(phone) if not vn: set_error(row, 'ERROR phone number is not verified with any user') return owner = vn.owner row['phone'] = strip_plus(phone) username = row.get('reporter') if username: user = CouchUser.get_by_username('%s@%s.commcarehq.org' % (username, domain)) if not user: set_error(row, 'ERROR reporter user does not exist') return if owner: if user and user._id != owner._id: set_error(row, 'ERROR phone number does not belong to user') return user = owner row['user'] = user # validate other fields try: row['timestamp'] = datetime.strptime(row['date'], '%Y-%m-%d') # TODO: allow time? except ValueError: set_error(row, 'ERROR invalid date format') return for k in data_cols: val = row[k] if val: try: int(val) except ValueError: set_error( row, 'ERROR invalid data value "%s" in column "%s"' % (val, k)) return if all(not row[k] for k in data_cols): set_error(row, 'ERROR stock report is empty') return
def ivr_in(request): """ Handles tropo call requests """ from tropo import Tropo if request.method == "POST": data = json.loads(request.body.decode('utf-8')) phone_number = data["session"]["from"]["id"] if phone_number: cleaned_number = strip_plus(phone_number) v = PhoneNumber.by_extensive_search(cleaned_number) else: v = None # Save the call entry msg = Call( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=SQLTropoBackend.get_api_id(), ) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() t = Tropo() t.reject() return HttpResponse(t.RenderJson()) else: return HttpResponseBadRequest("Bad Request")
def ivr_in(request): """ Handles tropo call requests """ if request.method == "POST": data = json.loads(request.body) phone_number = data["session"]["from"]["id"] # TODO: Implement tropo as an ivr backend. In the meantime, just log the call. if phone_number: cleaned_number = strip_plus(phone_number) v = VerifiedNumber.by_extensive_search(cleaned_number) else: v = None # Save the call entry msg = CallLog( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=TropoBackend.get_api_id(), ) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() t = Tropo() t.reject() return HttpResponse(t.RenderJson()) else: return HttpResponseBadRequest("Bad Request")
def send(self, msg, *args, **kwargs): if self.additional_params is not None: params = self.additional_params.copy() else: params = {} phone_number = msg.phone_number if self.include_plus: phone_number = clean_phone_number(phone_number) else: phone_number = strip_plus(phone_number) try: text = msg.text.encode("iso-8859-1") except UnicodeEncodeError: text = msg.text.encode("utf-8") params[self.message_param] = text params[self.number_param] = phone_number url_params = urlencode(params) if self.method == "GET": response = urlopen("%s?%s" % (self.url, url_params), timeout=settings.SMS_GATEWAY_TIMEOUT).read() else: response = urlopen(self.url, url_params, timeout=settings.SMS_GATEWAY_TIMEOUT).read()
def send(self, msg, orig_phone_number=None, *args, **kwargs): config = self.config phone_number = strip_plus(msg.phone_number) try: text = msg.text.encode("iso-8859-1") msg_type = "PM" except UnicodeEncodeError: text = msg.text.encode("utf_16_be").encode('hex').upper() msg_type = "UC" params = { "username": config.username, "pin": config.pin, "mnumber": phone_number, "message": text, "signature": config.sender_id, "msgType": msg_type, "splitAlgm": "concat", } url_params = urlencode(params) url = 'https://smsgw.sms.gov.in/failsafe/HttpLink' response = urlopen("%s?%s" % (url, url_params), timeout=settings.SMS_GATEWAY_TIMEOUT).read() response_code = self.get_response_code(response) if response_code != '000': self.handle_error(response_code, msg)
def ivr_in(request): """ Handles tropo call requests """ if request.method == "POST": data = json.loads(request.body) phone_number = data["session"]["from"]["id"] # TODO: Implement tropo as an ivr backend. In the meantime, just log the call. if phone_number: cleaned_number = strip_plus(phone_number) v = VerifiedNumber.by_extensive_search(cleaned_number) else: v = None # Save the call entry msg = CallLog( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=SQLTropoBackend.get_api_id(), ) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() t = Tropo() t.reject() return HttpResponse(t.RenderJson()) else: return HttpResponseBadRequest("Bad Request")
def send(self, msg, orig_phone_number=None, *args, **kwargs): config = self.config phone_number = strip_plus(msg.phone_number) if not self.destination_number_is_valid(phone_number): msg.set_system_error(SMS.ERROR_INVALID_DESTINATION_NUMBER) return try: text = msg.text.encode("iso-8859-1") msg_type = "PM" except UnicodeEncodeError: text = msg.text.encode("utf_16_be").encode('hex').upper() msg_type = "UC" params = { "username": config.username, "pin": config.pin, "mnumber": phone_number, "message": text, "signature": config.sender_id, "msgType": msg_type, "splitAlgm": "concat", } url_params = urlencode(params) url = 'https://smsgw.sms.gov.in/failsafe/HttpLink' response = urlopen("%s?%s" % (url, url_params), timeout=settings.SMS_GATEWAY_TIMEOUT).read() response_code = self.get_response_code(response) if response_code != '000': self.handle_error(response_code, msg)
def send_sms(self, msg, delay=True, *args, **kwargs): phone_number = strip_plus(msg.phone_number) if not phone_number.startswith("63"): raise MegamobileException("Only Filipino phone numbers are supported") phone_number = phone_number[2:] text = msg.text.encode("utf-8") pid = None if msg.in_reply_to: original_msg = SMSLog.get(msg.in_reply_to) pid = getattr(original_msg, "megamobile_pid", None) pid = pid or DEFAULT_PID setattr(msg, "megamobile_pid", pid) msg.save() params = urlencode({ "pid" : pid, "cel" : phone_number, "msg" : text, "src" : self.source_identifier, }) api_account_name = quote(self.api_account_name) url = "http://api.mymegamobile.com/%s?%s" % (api_account_name, params) response = urlopen(url, timeout=settings.SMS_GATEWAY_TIMEOUT).read()
def send(self, msg, *args, **kwargs): config = self.config if config.additional_params: params = config.additional_params.copy() else: params = {} phone_number = msg.phone_number if config.include_plus: phone_number = clean_phone_number(phone_number) else: phone_number = strip_plus(phone_number) try: text = msg.text.encode("iso-8859-1") except UnicodeEncodeError: text = msg.text.encode("utf-8") params[config.message_param] = text params[config.number_param] = phone_number url_params = urlencode(params) try: if config.method == "GET": response = urlopen("%s?%s" % (config.url, url_params), timeout=settings.SMS_GATEWAY_TIMEOUT).read() else: response = urlopen(config.url, url_params, timeout=settings.SMS_GATEWAY_TIMEOUT).read() except Exception as e: msg = "Error sending message from backend: '{}'\n\n{}".format(self.pk, str(e)) six.reraise(BackendProcessingException(msg), None, sys.exc_info()[2])
def test_sms_registration_no_user(self): # Test with no username no_username_phone_number = "+99912345678" incoming(no_username_phone_number, 'JOIN {} WORKER'.format(self.domain), self.backend.hq_api_id) self.assertIsNotNone(CommCareUser.get_by_username( format_username(strip_plus(no_username_phone_number), self.domain) ))
def phone_lookup(cls, view_name, phone_number, include_pending=False): # We use .one() here because the framework prevents duplicates # from being entered when a contact saves a number. # See CommCareMobileContactMixin.save_verified_number() from corehq.apps.sms.util import strip_plus v = cls.view(view_name, key=strip_plus(phone_number), include_docs=True).one() return v if (include_pending or (v and v.verified)) else None
def _update(self, bundle): should_save = False for key, value in bundle.data.items(): if key == 'phone_numbers' and getattr(bundle.obj, key, None) != value: bundle.obj.phone_numbers = [] for idx, phone_number in enumerate( bundle.data.get('phone_numbers', [])): bundle.obj.add_phone_number(strip_plus(phone_number)) if idx == 0: bundle.obj.set_default_phone_number( strip_plus(phone_number)) should_save = True elif getattr(bundle.obj, key, None) != value: setattr(bundle.obj, key, value) should_save = True return should_save
def get_or_create(cls, phone_number): """ phone_number - should be a string of digits """ phone_number = smsutil.strip_plus(phone_number) if not phone_number: return (None, False) return cls.objects.get_or_create(phone_number=phone_number)
def process_sms_registration(msg): """ This method handles registration via sms. Returns True if a contact was registered, False if not. To have a case register itself, do the following: 1) Select "Enable Case Registration Via SMS" in project settings, and fill in the associated Case Registration settings. 2) Text in "join <domain>", where <domain> is the domain to join. If the sending number does not exist in the system, a case will be registered tied to that number. The "join" keyword can be any keyword in REGISTRATION_KEYWORDS. This is meant to support multiple translations. To have a mobile worker register itself, do the following: NOTE: This is not yet implemented and may change slightly. 1) Select "Enable Mobile Worker Registration via SMS" in project settings. 2) Text in "join <domain> worker", where <domain> is the domain to join. If the sending number does not exist in the system, a PendingCommCareUser object will be created, tied to that number. The "join" and "worker" keywords can be any keyword in REGISTRATION_KEYWORDS and REGISTRATION_MOBILE_WORKER_KEYWORDS, respectively. This is meant to support multiple translations. 3) A domain admin will have to approve the addition of the mobile worker before a CommCareUser can actually be created. """ registration_processed = False text_words = msg.text.upper().split() keyword1 = text_words[0] if len(text_words) > 0 else "" keyword2 = text_words[1].lower() if len(text_words) > 1 else "" keyword3 = text_words[2] if len(text_words) > 2 else "" if keyword1 in REGISTRATION_KEYWORDS and keyword2 != "": domain = Domain.get_by_name(keyword2, strict=True) if domain is not None: if domain_has_privilege(domain, privileges.INBOUND_SMS): if keyword3 in REGISTRATION_MOBILE_WORKER_KEYWORDS and domain.sms_mobile_worker_registration_enabled: #TODO: Register a PendingMobileWorker object that must be approved by a domain admin pass elif domain.sms_case_registration_enabled: register_sms_contact( domain=domain.name, case_type=domain.sms_case_registration_type, case_name="unknown", user_id=domain.sms_case_registration_user_id, contact_phone_number=strip_plus(msg.phone_number), contact_phone_number_is_verified="1", owner_id=domain.sms_case_registration_owner_id, ) registration_processed = True msg.domain = domain.name msg.save() return registration_processed
def clean_phone_numbers(self): value = self.cleaned_data.get('phone_numbers', '') phone_list = [strip_plus(s.strip()) for s in value.split(',')] phone_list = [phone for phone in phone_list if phone] if len(phone_list) == 0: raise ValidationError(_("This field is required.")) for phone_number in phone_list: validate_phone_number(phone_number) return list(set(phone_list))
def process_sms_registration(msg): """ This method handles registration via sms. Returns True if a contact was registered, False if not. To have a case register itself, do the following: 1) Select "Enable Case Registration Via SMS" in project settings, and fill in the associated Case Registration settings. 2) Text in "join <domain>", where <domain> is the domain to join. If the sending number does not exist in the system, a case will be registered tied to that number. The "join" keyword can be any keyword in REGISTRATION_KEYWORDS. This is meant to support multiple translations. To have a mobile worker register itself, do the following: NOTE: This is not yet implemented and may change slightly. 1) Select "Enable Mobile Worker Registration via SMS" in project settings. 2) Text in "join <domain> worker", where <domain> is the domain to join. If the sending number does not exist in the system, a PendingCommCareUser object will be created, tied to that number. The "join" and "worker" keywords can be any keyword in REGISTRATION_KEYWORDS and REGISTRATION_MOBILE_WORKER_KEYWORDS, respectively. This is meant to support multiple translations. 3) A domain admin will have to approve the addition of the mobile worker before a CommCareUser can actually be created. """ registration_processed = False text_words = msg.text.upper().split() keyword1 = text_words[0] if len(text_words) > 0 else "" keyword2 = text_words[1].lower() if len(text_words) > 1 else "" keyword3 = text_words[2] if len(text_words) > 2 else "" if keyword1 in REGISTRATION_KEYWORDS and keyword2 != "": domain = Domain.get_by_name(keyword2, strict=True) if domain is not None: if keyword3 in REGISTRATION_MOBILE_WORKER_KEYWORDS and domain.sms_mobile_worker_registration_enabled: #TODO: Register a PendingMobileWorker object that must be approved by a domain admin pass elif domain.sms_case_registration_enabled: register_sms_contact( domain=domain.name, case_type=domain.sms_case_registration_type, case_name="unknown", user_id=domain.sms_case_registration_user_id, contact_phone_number=strip_plus(msg.phone_number), contact_phone_number_is_verified="1", owner_id=domain.sms_case_registration_owner_id, ) msg.domain = domain.name msg.save() registration_processed = True return registration_processed
def validate_row(row, domain, data_cols): """pre-validate the information in a particular import row: valid location, reporting user, and data formats """ # identify location loc_code = row.get('outlet_code') or row.get('site_code') row['loc'] = get_supply_point(domain, loc_code)['case'] if row['loc'] is None: set_error(row, 'ERROR location code is invalid') return # identify user phone = row.get('phone') owner = None if phone: vn = VerifiedNumber.by_phone(phone) if not vn: set_error(row, 'ERROR phone number is not verified with any user') return owner = vn.owner row['phone'] = strip_plus(phone) username = row.get('reporter') if username: user = CouchUser.get_by_username('%s@%s.commcarehq.org' % (username, domain)) if not user: set_error(row, 'ERROR reporter user does not exist') return if owner: if user and user._id != owner._id: set_error(row, 'ERROR phone number does not belong to user') return user = owner row['user'] = user # validate other fields try: row['timestamp'] = datetime.strptime(row['date'], '%Y-%m-%d') # TODO: allow time? except ValueError: set_error(row, 'ERROR invalid date format') return for k in data_cols: val = row[k] if val: try: int(val) except ValueError: set_error(row, 'ERROR invalid data value "%s" in column "%s"' % (val, k)) return if all(not row[k] for k in data_cols): set_error(row, 'ERROR stock report is empty') return
def get_phone_number(phone_number): """ Only send to Indian phone numbers. Also remove the country code before making the request. """ phone_number = strip_plus(phone_number) if re.match(r'^(91)[6-9]\d{9}$', phone_number): return phone_number[2:] raise InvalidDestinationNumber()
def get_phone_number(phone_number): """ Only send to Indian phone numbers. Also remove the country code before making the request. """ phone_number = strip_plus(phone_number) if phone_number.startswith('91') and len(phone_number) > 2: return phone_number[2:] raise InvalidDestinationNumber()
def handle_error(self, response, msg): phone = strip_plus(msg.phone_number) if not (phone.startswith(GHANA_COUNTRY_CODE) and len(phone) == GHANA_PHONE_LENGTH): msg.set_system_error(SMS.ERROR_INVALID_DESTINATION_NUMBER) return data = self.get_additional_data(response) raise SMSGHException("Error with the SMSGH backend. " "Response Code: %s, Subcode: %s. See " "http://developers.smsgh.com/documentations/sendmessage#handlingerrors " "for details. " % (response.status_code, data.get('Status')))
def get_ivr_backend(recipient, verified_number=None, unverified_number=None): if verified_number and verified_number.ivr_backend_id: return MobileBackend.get(verified_number.ivr_backend_id) else: phone_number = verified_number.phone_number if verified_number else unverified_number phone_number = strip_plus(str(phone_number)) prefixes = settings.IVR_BACKEND_MAP.keys() prefixes = sorted(prefixes, key=lambda x: len(x), reverse=True) for prefix in prefixes: if phone_number.startswith(prefix): return MobileBackend.get(settings.IVR_BACKEND_MAP[prefix]) return None
def _update_default_phone_number(user, phone_number, user_change_logger): old_phone_numbers = set(user.phone_numbers) new_phone_numbers = set(user.phone_numbers) if not isinstance(phone_number, str): raise InvalidFormatException('default_phone_number', 'string') formatted_phone_number = strip_plus(phone_number) new_phone_numbers.add(formatted_phone_number) user.set_default_phone_number(formatted_phone_number) if user_change_logger: _log_phone_number_change(new_phone_numbers, old_phone_numbers, user_change_logger)
def get_params(self, msg_obj): config = self.config message = msg_obj.text.encode('utf_16_be').encode('hex').upper() message_type = LONG_UNICODE_MSG_TYPE return { 'usr': config.username, 'pass': config.password, 'msisdn': strip_plus(msg_obj.phone_number), 'sid': config.sender_id, 'mt': message_type, 'msg': message, }
def get_ivr_backend(recipient, verified_number=None, unverified_number=None): if verified_number and verified_number.ivr_backend_id: return MobileBackend.get(verified_number.ivr_backend_id) else: phone_number = (verified_number.phone_number if verified_number else unverified_number) phone_number = strip_plus(str(phone_number)) prefixes = settings.IVR_BACKEND_MAP.keys() prefixes = sorted(prefixes, key=lambda x: len(x), reverse=True) for prefix in prefixes: if phone_number.startswith(prefix): return MobileBackend.get(settings.IVR_BACKEND_MAP[prefix]) return None
def _update(self, bundle): should_save = False for key, value in bundle.data.items(): if getattr(bundle.obj, key, None) != value: if key == 'phone_numbers': bundle.obj.phone_numbers = [] for idx, phone_number in enumerate(bundle.data.get('phone_numbers', [])): bundle.obj.add_phone_number(strip_plus(phone_number)) if idx == 0: bundle.obj.set_default_phone_number(strip_plus(phone_number)) should_save = True elif key == 'groups': bundle.obj.set_groups(bundle.data.get("groups", [])) should_save = True elif key in ['email', 'username']: setattr(bundle.obj, key, value.lower()) should_save = True else: setattr(bundle.obj, key, value) should_save = True return should_save
def test_unicode(self): queued_sms = QueuedSMS( phone_number=TEST_PHONE_NUMBER, text=TEST_UNICODE_MESSAGE, ) self.assertEqual( self.backend.get_params(queued_sms), { 'usr': TEST_USERNAME, 'pass': TEST_PASSWORD, 'msisdn': strip_plus(TEST_PHONE_NUMBER), 'sid': TEST_SENDER_ID, 'mt': LONG_UNICODE_MSG_TYPE, 'msg': '0928092E0938094D09240947', })
def _update_phone_numbers(user, phone_numbers, user_change_logger): old_phone_numbers = set(user.phone_numbers) new_phone_numbers = set() user.phone_numbers = [] for idx, phone_number in enumerate(phone_numbers): formatted_phone_number = strip_plus(phone_number) new_phone_numbers.add(formatted_phone_number) user.add_phone_number(formatted_phone_number) if idx == 0: user.set_default_phone_number(formatted_phone_number) if user_change_logger: _log_phone_number_change(new_phone_numbers, old_phone_numbers, user_change_logger)
def test_ascii(self): queued_sms = QueuedSMS( phone_number=TEST_PHONE_NUMBER, text=TEST_TEXT_MESSAGE, ) self.assertEqual( self.backend.get_params(queued_sms), { 'usr': TEST_USERNAME, 'pass': TEST_PASSWORD, 'msisdn': strip_plus(TEST_PHONE_NUMBER), 'sid': TEST_SENDER_ID, 'mt': LONG_TEXT_MSG_TYPE, 'msg': TEST_TEXT_MESSAGE, })
def _update(self, bundle): should_save = False for key, value in bundle.data.items(): if getattr(bundle.obj, key, None) != value: if key == 'phone_numbers': bundle.obj.phone_numbers = [] for idx, phone_number in enumerate( bundle.data.get('phone_numbers', [])): bundle.obj.add_phone_number(strip_plus(phone_number)) if idx == 0: bundle.obj.set_default_phone_number( strip_plus(phone_number)) should_save = True elif key == 'groups': bundle.obj.set_groups(bundle.data.get("groups", [])) should_save = True elif key in ['email', 'username']: setattr(bundle.obj, key, value.lower()) should_save = True else: setattr(bundle.obj, key, value) should_save = True return should_save
def test_params(self): self.queued_sms.text = TEST_TEXT_MESSAGE params = self.vertex_backend.populate_params(self.queued_sms) self.assertEqual(params['username'], TEST_USERNAME) self.assertEqual(params['pass'], TEST_PASSWORD) self.assertEqual(params['senderid'], TEST_SENDER_ID) self.assertEqual(params['response'], 'Y') self.assertEqual(params['dest_mobileno'], strip_plus(TEST_PHONE_NUMBER)) self.assertEqual(params['msgtype'], TEXT_MSG_TYPE) self.assertEqual(params['message'], TEST_TEXT_MESSAGE) self.queued_sms.text = TEST_UNICODE_MESSAGE params = self.vertex_backend.populate_params(self.queued_sms) self.assertEqual(params['message'], TEST_UNICODE_MESSAGE.encode('utf-8')) self.assertEqual(params['msgtype'], UNICODE_MSG_TYPE)
def get_verified_number(self, phone=None): """ Retrieves this contact's verified number entry by (self.doc_type, self._id). return the VerifiedNumber entry """ from corehq.apps.sms.util import strip_plus verified = self.get_verified_numbers(True) if not phone: # for backwards compatibility with code that assumes only one verified phone # if len(verified) > 0: return sorted(verified.iteritems())[0][1] else: return None return verified.get(strip_plus(phone))
def test_ascii(self): queued_sms = QueuedSMS( phone_number=TEST_PHONE_NUMBER, text=TEST_TEXT_MESSAGE, ) self.assertEqual( self.backend.get_params(queued_sms), { 'usr': TEST_USERNAME, 'pass': TEST_PASSWORD, 'msisdn': strip_plus(TEST_PHONE_NUMBER), 'sid': TEST_SENDER_ID, 'mt': LONG_TEXT_MSG_TYPE, 'msg': TEST_TEXT_MESSAGE, } )
def log_call(phone_number, gateway_session_id, backend_module=None): cleaned_number = strip_plus(phone_number) v = VerifiedNumber.by_extensive_search(cleaned_number) call = CallLog( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=backend_module.API_ID if backend_module else None, gateway_session_id=gateway_session_id, ) if v: call.domain = v.domain call.couch_recipient_doc_type = v.owner_doc_type call.couch_recipient = v.owner_id call.save()
def test_unicode(self): queued_sms = QueuedSMS( phone_number=TEST_PHONE_NUMBER, text=TEST_UNICODE_MESSAGE, ) self.assertEqual( self.backend.get_params(queued_sms), { 'usr': TEST_USERNAME, 'pass': TEST_PASSWORD, 'msisdn': strip_plus(TEST_PHONE_NUMBER), 'sid': TEST_SENDER_ID, 'mt': LONG_UNICODE_MSG_TYPE, 'msg': '0928092E0938094D09240947', } )
def log_call(phone_number, gateway_session_id, backend=None): cleaned_number = strip_plus(phone_number) v = PhoneNumber.by_extensive_search(cleaned_number) call = Call( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=backend.get_api_id() if backend else None, backend_id=backend.couch_id if backend else None, gateway_session_id=gateway_session_id, ) if v: call.domain = v.domain call.couch_recipient_doc_type = v.owner_doc_type call.couch_recipient = v.owner_id call.save()
def process_incoming_message(*args, **kwargs): backend = TelerivetBackend.by_webhook_secret(kwargs["secret"]) if backend is None: # Ignore the message if the webhook secret is not recognized return from_number = strip_plus(kwargs["from_number"]) if backend.country_code: if not from_number.startswith(backend.country_code): from_number = "%s%s" % (backend.country_code, from_number) if kwargs["event"] == EVENT_INCOMING: if kwargs["message_type"] == MESSAGE_TYPE_SMS: incoming_sms(from_number, kwargs["content"], TelerivetBackend.get_api_id()) elif kwargs["message_type"] == MESSAGE_TYPE_CALL: incoming_ivr(from_number, None, "TELERIVET-%s" % kwargs["message_id"], None)
def initiate_outbound_call(call_log_entry, logged_subevent, ivr_data=None, *args, **kwargs): """ Expected kwargs: api_key Same expected return value as corehq.apps.ivr.api.initiate_outbound_call """ phone_number = strip_plus(call_log_entry.phone_number) if phone_number.startswith('91'): phone_number = '0%s' % phone_number[2:] else: log_error(MessagingEvent.ERROR_UNSUPPORTED_COUNTRY, call_log_entry, logged_subevent) return True response = invoke_kookoo_outbound_api(phone_number, kwargs['api_key'], kwargs.get('is_test', False)) status, message = get_status_and_message(response) do_not_retry = False if status == 'queued': call_log_entry.error = False call_log_entry.gateway_session_id = 'KOOKOO-%s' % message if ivr_data: set_first_ivr_response(call_log_entry, call_log_entry.gateway_session_id, ivr_data, get_http_response_string) elif status == 'error': call_log_entry.error = True call_log_entry.error_message = message if (message.strip().upper() in [ 'CALLS WILL NOT BE MADE BETWEEN 9PM TO 9AM.', 'PHONE NUMBER IN DND LIST', ]): # These are error messages that come from KooKoo and # are indicative of non-recoverable errors, so we # wouldn't benefit from retrying the call. do_not_retry = True logged_subevent.error(MessagingEvent.ERROR_GATEWAY_ERROR) else: log_error(MessagingEvent.ERROR_GATEWAY_ERROR, call_log_entry, logged_subevent) call_log_entry.save() return not call_log_entry.error or do_not_retry
def apply_leniency(contact_phone_number): """ The documentation says that contact_phone_number should be in international format and consist of only digits. However, we can apply some leniency to avoid common mistakes. Returns None if an unsupported data type is passed in. """ from corehq.apps.sms.util import strip_plus # Decimal preserves trailing zeroes, so it's ok if isinstance(contact_phone_number, (int, long, Decimal)): contact_phone_number = str(contact_phone_number) if isinstance(contact_phone_number, basestring): chars = re.compile(r"(\s|-|\.)+") contact_phone_number = chars.sub("", contact_phone_number) contact_phone_number = strip_plus(contact_phone_number) else: contact_phone_number = None return contact_phone_number
def get_params(self, msg_obj): config = self.config try: message = msg_obj.text message.encode('ascii') message_type = LONG_TEXT_MSG_TYPE except UnicodeEncodeError: message = codecs.encode(codecs.encode(msg_obj.text, 'utf_16_be'), 'hex').decode('utf-8').upper() message_type = LONG_UNICODE_MSG_TYPE return { 'usr': config.username, 'pass': config.password, 'msisdn': strip_plus(msg_obj.phone_number), 'sid': config.sender_id, 'mt': message_type, 'msg': message, }
def get_json_payload(self, msg_obj): config = self.config text, content_type = self.get_text_and_content_type(msg_obj) # 'vp' is validity period, and we set it to the maximum value (1 day) return { 'ver': '1.0', 'key': self.get_auth_key(), 'messages': [ { 'dest': [strip_plus(msg_obj.phone_number)], 'send': config.sender_id, 'text': text, 'type': content_type, 'vp': '1440', }, ], }