def global_keyword_start(verified_number, text, msg, text_words, open_sessions): outbound_metadata = MessageMetadata(workflow=WORKFLOW_KEYWORD, ) if len(text_words) > 1: keyword = text_words[1] k = Keyword.get_keyword(verified_number.domain, keyword) if k: if not contact_can_use_keyword(verified_number, k): return False process_survey_keyword_actions(verified_number, k, text[6:].strip(), msg) else: message = get_message(MSG_KEYWORD_NOT_FOUND, verified_number, (keyword, )) send_sms_to_verified_number(verified_number, message, metadata=outbound_metadata) else: message = get_message(MSG_START_KEYWORD_USAGE, verified_number, (text_words[0], )) send_sms_to_verified_number(verified_number, message, metadata=outbound_metadata) return True
def test_opt_out_and_opt_in(self): self.assertEqual(PhoneBlacklist.objects.count(), 0) incoming('99912345678', 'join opt-test', 'GVI') v = PhoneNumber.get_two_way_number('99912345678') self.assertIsNotNone(v) incoming('99912345678', 'stop', 'GVI') self.assertEqual(PhoneBlacklist.objects.count(), 1) phone_number = PhoneBlacklist.objects.get(phone_number='99912345678') self.assertFalse(phone_number.send_sms) self.assertEqual(phone_number.domain, self.domain) self.assertIsNotNone(phone_number.last_sms_opt_out_timestamp) self.assertIsNone(phone_number.last_sms_opt_in_timestamp) sms = self.get_last_sms('+99912345678') self.assertEqual(sms.direction, 'O') self.assertEqual(sms.text, get_message(MSG_OPTED_OUT, context=('START', ))) incoming('99912345678', 'start', 'GVI') self.assertEqual(PhoneBlacklist.objects.count(), 1) phone_number = PhoneBlacklist.objects.get(phone_number='99912345678') self.assertTrue(phone_number.send_sms) self.assertEqual(phone_number.domain, self.domain) self.assertIsNotNone(phone_number.last_sms_opt_out_timestamp) self.assertIsNotNone(phone_number.last_sms_opt_in_timestamp) sms = self.get_last_sms('+99912345678') self.assertEqual(sms.direction, 'O') self.assertEqual(sms.text, get_message(MSG_OPTED_IN, context=('STOP', )))
def test_opt_out_and_opt_in(self): self.assertEqual(PhoneBlacklist.objects.count(), 0) incoming('99912345678', 'join opt-test', 'GVI') v = PhoneNumber.get_two_way_number('99912345678') self.assertIsNotNone(v) incoming('99912345678', 'stop', 'GVI') self.assertEqual(PhoneBlacklist.objects.count(), 1) phone_number = PhoneBlacklist.objects.get(phone_number='99912345678') self.assertFalse(phone_number.send_sms) self.assertEqual(phone_number.domain, self.domain) self.assertIsNotNone(phone_number.last_sms_opt_out_timestamp) self.assertIsNone(phone_number.last_sms_opt_in_timestamp) sms = self.get_last_sms('+99912345678') self.assertEqual(sms.direction, 'O') self.assertEqual(sms.text, get_message(MSG_OPTED_OUT, context=('START',))) incoming('99912345678', 'start', 'GVI') self.assertEqual(PhoneBlacklist.objects.count(), 1) phone_number = PhoneBlacklist.objects.get(phone_number='99912345678') self.assertTrue(phone_number.send_sms) self.assertEqual(phone_number.domain, self.domain) self.assertIsNotNone(phone_number.last_sms_opt_out_timestamp) self.assertIsNotNone(phone_number.last_sms_opt_in_timestamp) sms = self.get_last_sms('+99912345678') self.assertEqual(sms.direction, 'O') self.assertEqual(sms.text, get_message(MSG_OPTED_IN, context=('STOP',)))
def send_verification(domain, user, phone_number, logged_event): backend = SQLMobileBackend.load_default_by_phone_and_domain( SQLMobileBackend.SMS, phone_number, domain=domain) reply_phone = backend.reply_to_phone_number subevent = logged_event.create_subevent_for_single_sms( user.doc_type, user.get_id) if reply_phone: message = messages.get_message( messages.MSG_VERIFICATION_START_WITH_REPLY, context=(user.raw_username, reply_phone), domain=domain, language=user.get_language_code()) else: message = messages.get_message( messages.MSG_VERIFICATION_START_WITHOUT_REPLY, context=(user.raw_username, ), domain=domain, language=user.get_language_code()) send_sms(domain, user, phone_number, message, metadata=MessageMetadata(messaging_subevent_id=subevent.pk)) subevent.completed()
def send_verification(domain, user, phone_number, logged_event): backend = SQLMobileBackend.load_default_by_phone_and_domain( SQLMobileBackend.SMS, phone_number, domain=domain ) reply_phone = backend.reply_to_phone_number subevent = logged_event.create_subevent_for_single_sms( user.doc_type, user.get_id ) if reply_phone: message = messages.get_message( messages.MSG_VERIFICATION_START_WITH_REPLY, context=(user.raw_username, reply_phone), domain=domain, language=user.get_language_code() ) else: message = messages.get_message( messages.MSG_VERIFICATION_START_WITHOUT_REPLY, context=(user.raw_username,), domain=domain, language=user.get_language_code() ) send_sms(domain, user, phone_number, message, metadata=MessageMetadata(messaging_subevent_id=subevent.pk)) subevent.completed()
def process_incoming(msg): v = PhoneNumber.by_phone(msg.phone_number, include_pending=True) if v: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.location_id = get_location_id_by_verified_number(v) msg.save() elif msg.domain_scope: msg.domain = msg.domain_scope msg.save() can_receive_sms = PhoneBlacklist.can_receive_sms(msg.phone_number) opt_in_keywords, opt_out_keywords = get_opt_keywords(msg) domain = v.domain if v else None if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms: if PhoneBlacklist.opt_out_sms(msg.phone_number, domain=domain): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],)) if v: send_sms_to_verified_number(v, text, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms: if PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],)) if v: send_sms_to_verified_number(v, text) else: send_sms(msg.domain, None, msg.phone_number, text) else: handled = False is_verified = v is not None and v.verified if msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS): handled = load_and_call(settings.CUSTOM_SMS_HANDLERS, v, msg.text, msg) if not handled and is_verified and is_contact_active(v.domain, v.owner_doc_type, v.owner_id): handled = load_and_call(settings.SMS_HANDLERS, v, msg.text, msg) if not handled and not is_verified: handled = process_pre_registration(msg) if not handled: handled = process_sms_registration(msg) if not handled: import verify verify.process_verification(v, msg) # If the sms queue is enabled, then the billable gets created in remove_from_queue() if ( not settings.SMS_QUEUE_ENABLED and msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS) ): create_billable_for_sms(msg)
def process_incoming(msg): v = PhoneNumber.by_phone(msg.phone_number, include_pending=True) if v is not None and v.verified: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.location_id = get_location_id_by_verified_number(v) msg.save() if msg.domain_scope: # only process messages for phones known to be associated with this domain if v is None or v.domain != msg.domain_scope: raise DomainScopeValidationError( 'Attempted to simulate incoming sms from phone number not ' \ 'verified with this domain' ) can_receive_sms = PhoneBlacklist.can_receive_sms(msg.phone_number) opt_in_keywords, opt_out_keywords = get_opt_keywords(msg) domain = v.domain if v else None if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms: if PhoneBlacklist.opt_out_sms(msg.phone_number, domain=domain): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],)) if v: send_sms_to_verified_number(v, text, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms: if PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],)) if v: send_sms_to_verified_number(v, text) else: send_sms(msg.domain, None, msg.phone_number, text) elif v is not None and v.verified: if ( domain_has_privilege(msg.domain, privileges.INBOUND_SMS) and is_contact_active(v.domain, v.owner_doc_type, v.owner_id) ): for h in settings.SMS_HANDLERS: try: handler = to_function(h) except: notify_exception(None, message=('error loading sms handler: %s' % h)) continue try: was_handled = handler(v, msg.text, msg=msg) except Exception, e: log_sms_exception(msg) was_handled = False if was_handled: break
def process_incoming(msg, delay=True): v = VerifiedNumber.by_phone(msg.phone_number, include_pending=True) if v is not None and v.verified: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain contact = v.owner if isinstance(contact, CommCareUser) and hasattr(contact, 'location_id'): msg.location_id = contact.location_id msg.save() if msg.domain_scope: # only process messages for phones known to be associated with this domain if v is None or v.domain != msg.domain_scope: raise DomainScopeValidationError( 'Attempted to simulate incoming sms from phone number not ' \ 'verified with this domain' ) can_receive_sms = PhoneNumber.can_receive_sms(msg.phone_number) opt_in_keywords, opt_out_keywords = get_opt_keywords(msg) if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms: if PhoneNumber.opt_out_sms(msg.phone_number): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],)) if v: send_sms_to_verified_number(v, text, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms: if PhoneNumber.opt_in_sms(msg.phone_number): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],)) if v: send_sms_to_verified_number(v, text) else: send_sms(msg.domain, None, msg.phone_number, text) elif v is not None and v.verified: if domain_has_privilege(msg.domain, privileges.INBOUND_SMS): for h in settings.SMS_HANDLERS: try: handler = to_function(h) except: notify_exception(None, message=('error loading sms handler: %s' % h)) continue try: was_handled = handler(v, msg.text, msg=msg) except Exception, e: log_sms_exception(msg) was_handled = False if was_handled: break
def process_username(username, domain): from corehq.apps.users.forms import (clean_mobile_worker_username, get_mobile_worker_max_username_length) max_length = get_mobile_worker_max_username_length(domain) return clean_mobile_worker_username( domain, username, name_too_long_message=get_message(MSG_USERNAME_TOO_LONG, context=(username, max_length)), name_exists_message=get_message(MSG_DUPLICATE_USERNAME, context=(username,)) )
def parse_structured_sms_named_args(args, action, verified_number=None): """ Returns a dictionary of {xpath: answer} """ xpath_answer = {} for answer in args: answer = answer.strip() answer_upper = answer.upper() if action.named_args_separator is not None: # A separator is used for naming arguments; for example, the "=" # in "register name=joe age=25" answer_parts = answer.partition(action.named_args_separator) if answer_parts[1] != action.named_args_separator: error_msg = get_message(MSG_EXPECTED_NAMED_ARGS_SEPARATOR, verified_number, (action.named_args_separator, )) raise StructuredSMSException(response_text=error_msg) else: arg_name = answer_parts[0].upper().strip() xpath = action.named_args.get(arg_name, None) if xpath is not None: if xpath in xpath_answer: error_msg = get_message(MSG_MULTIPLE_ANSWERS_FOUND, verified_number, (arg_name, )) raise StructuredSMSException(response_text=error_msg) xpath_answer[xpath] = answer_parts[2].strip() else: # Ignore unexpected named arguments pass else: # No separator is used for naming arguments # for example, "update a100 b34 c5" matches = 0 for k, v in action.named_args.items(): if answer_upper.startswith(k): matches += 1 if matches > 1: error_msg = get_message(MSG_MULTIPLE_QUESTIONS_MATCH, verified_number, (answer, )) raise StructuredSMSException(response_text=error_msg) if v in xpath_answer: error_msg = get_message(MSG_MULTIPLE_ANSWERS_FOUND, verified_number, (k, )) raise StructuredSMSException(response_text=error_msg) xpath_answer[v] = answer[len(k):].strip() if matches == 0: # Ignore unexpected named arguments pass return xpath_answer
def _handle_structured_sms(domain, args, contact_id, session_id, first_question, verified_number, xpath_answer=None): form_complete = False current_question = first_question internal_error_msg = get_message(MSG_TOUCHFORMS_DOWN, verified_number) used_named_args = xpath_answer is not None answer_num = 0 while not form_complete: if current_question.is_error: error_msg = current_question.text_prompt or internal_error_msg raise StructuredSMSException(response_text=error_msg, xformsresponse=current_question) xpath = current_question.event._dict["binding"] if used_named_args and xpath in xpath_answer: valid, answer, error_msg = validate_answer(current_question.event, xpath_answer[xpath], verified_number) if not valid: raise StructuredSMSException(response_text=error_msg, xformsresponse=current_question) elif not used_named_args and answer_num < len(args): answer = args[answer_num].strip() valid, answer, error_msg = validate_answer(current_question.event, answer, verified_number) if not valid: raise StructuredSMSException(response_text=error_msg, xformsresponse=current_question) else: # We're out of arguments, so try to leave each remaining question # blank and continue answer = "" if current_question.event._dict.get("required", False): error_msg = get_message(MSG_FIELD_REQUIRED, verified_number) raise StructuredSMSException(response_text=error_msg, xformsresponse=current_question) responses = get_responses(domain, session_id, answer) current_question = responses[-1] form_complete = is_form_complete(current_question) answer_num += 1
def send_keyword_response(vn, message_id, logged_event): subevent = logged_event.create_subevent_for_single_sms( vn.owner_doc_type, vn.owner_id) metadata = MessageMetadata( workflow=WORKFLOW_KEYWORD, messaging_subevent_id=subevent.pk, ) message = get_message(message_id, vn) send_sms_to_verified_number(vn, message, metadata=metadata) subevent.completed()
def process_username(username, domain): """ Loosely based on code from apps/users/forms.py:255 """ from corehq.apps.users.forms import validate_username max_len_username = 80 if len(username) > max_len_username: raise forms.ValidationError(get_message(MSG_USERNAME_TOO_LONG, context=(username, max_len_username))) # Check if the username contains invalid characters w/ django checker validate_username('*****@*****.**' % username) username = format_username(username, domain) num_couch_users = len(CouchUser.view("users/by_username", key=username, reduce=False)) if num_couch_users > 0: raise forms.ValidationError(get_message(MSG_DUPLICATE_USERNAME, context=(username,))) return username
def process_username(username, domain): """ Loosely based on code from apps/users/forms.py:255 """ from corehq.apps.users.forms import validate_username max_len_username = 80 if len(username) > max_len_username: raise forms.ValidationError( get_message(MSG_USERNAME_TOO_LONG, context=(username, max_len_username))) # Check if the username contains invalid characters w/ django checker validate_username('*****@*****.**' % username) username = format_username(username, domain) num_couch_users = len( CouchUser.view("users/by_username", key=username, reduce=False)) if num_couch_users > 0: raise forms.ValidationError( get_message(MSG_DUPLICATE_USERNAME, context=(username, ))) return username
def process_verification(v, msg, verification_keywords=None, create_subevent_for_inbound=True): verification_keywords = verification_keywords or ['123'] logged_event = MessagingEvent.get_current_verification_event( v.domain, v.owner_id, v.phone_number) if not logged_event: logged_event = MessagingEvent.create_verification_event(v.domain, v.owner) msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id if create_subevent_for_inbound: subevent = logged_event.create_subevent_for_single_sms( v.owner_doc_type, v.owner_id ) subevent.completed() msg.messaging_subevent_id = subevent.pk msg.save() if ( not domain_has_privilege(msg.domain, privileges.INBOUND_SMS) or not verification_response_ok(msg.text, verification_keywords) ): return False v.set_two_way() v.set_verified() v.save() logged_event.completed() subevent = logged_event.create_subevent_for_single_sms( v.owner_doc_type, v.owner_id ) message = messages.get_message( messages.MSG_VERIFICATION_SUCCESSFUL, verified_number=v ) send_sms_to_verified_number(v, message, metadata=MessageMetadata(messaging_subevent_id=subevent.pk)) subevent.completed() return True
def process_verification(v, msg): if not v or v.verified: return logged_event = MessagingEvent.get_current_verification_event( v.domain, v.owner_id, v.phone_number) if not logged_event: logged_event = MessagingEvent.create_verification_event(v.domain, v.owner) subevent = logged_event.create_subevent_for_single_sms( v.owner_doc_type, v.owner_id ) subevent.completed() msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.messaging_subevent_id = subevent.pk msg.save() if ( not domain_has_privilege(msg.domain, privileges.INBOUND_SMS) or not verification_response_ok(msg.text) ): return v.verified = True v.save() logged_event.completed() subevent = logged_event.create_subevent_for_single_sms( v.owner_doc_type, v.owner_id ) message = messages.get_message( messages.MSG_VERIFICATION_SUCCESSFUL, verified_number=v ) send_sms_to_verified_number(v, message, metadata=MessageMetadata(messaging_subevent_id=subevent.pk)) subevent.completed()
def process_verification(v, msg): if not v or v.verified: return logged_event = MessagingEvent.get_current_verification_event( v.domain, v.owner_id, v.phone_number) if not logged_event: logged_event = MessagingEvent.create_verification_event( v.domain, v.owner) subevent = logged_event.create_subevent_for_single_sms( v.owner_doc_type, v.owner_id) subevent.completed() msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.messaging_subevent_id = subevent.pk msg.save() if (not domain_has_privilege(msg.domain, privileges.INBOUND_SMS) or not verification_response_ok(msg.text)): return v.verified = True v.save() logged_event.completed() subevent = logged_event.create_subevent_for_single_sms( v.owner_doc_type, v.owner_id) message = messages.get_message(messages.MSG_VERIFICATION_SUCCESSFUL, verified_number=v) send_sms_to_verified_number( v, message, metadata=MessageMetadata(messaging_subevent_id=subevent.pk)) subevent.completed()
def register_sms_user( username, cleaned_phone_number, domain, send_welcome_sms=False, admin_alert_emails=None ): try: user_data = {} username = process_username(username, domain) password = random_password() new_user = CommCareUser.create( domain, username, password, created_by=None, created_via=USER_CHANGE_VIA_SMS, metadata=user_data ) new_user.add_phone_number(cleaned_phone_number) new_user.save() entry = new_user.get_or_create_phone_entry(cleaned_phone_number) entry.set_two_way() entry.set_verified() entry.save() if send_welcome_sms: send_sms( domain, None, cleaned_phone_number, get_message(MSG_REGISTRATION_WELCOME_MOBILE_WORKER, domain=domain) ) if admin_alert_emails: send_admin_registration_alert(domain, admin_alert_emails, new_user) except ValidationError as e: send_sms(domain, None, cleaned_phone_number, e.messages[0]) return False else: return True
def form_session_handler(verified_number, text, msg): """ The form session handler will use the inbound text to answer the next question in the open SQLXformsSession for the associated contact. If no session is open, the handler passes. If multiple sessions are open, they are all closed and an error message is displayed to the user. """ with critical_section_for_smsforms_sessions(verified_number.owner_id): if toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled( verified_number.domain): channel = get_channel_for_contact(verified_number.owner_id, verified_number.phone_number) running_session_info = XFormsSessionSynchronization.get_running_session_info_for_channel( channel) if running_session_info.session_id: session = SQLXFormsSession.by_session_id( running_session_info.session_id) if session.connection_id != verified_number.owner_id: notify_error( "SMS response contact does not match open session contact", details={ "session_id": session.session_id, "phone_number_id": verified_number.couch_id, "message_id": msg.couch_id }) session.mark_completed( False) # this will also release the channel session.save() send_sms_to_verified_number( verified_number, get_message(MSG_GENERIC_ERROR, verified_number)) return True if not session.session_is_open: # This should never happen. But if it does we should set the channel free # and act like there was no available session notify_error( "The supposedly running session was not open and was released. " 'No known way for this to happen, so worth investigating.' ) XFormsSessionSynchronization.clear_stale_channel_claim( channel) session = None else: session = None else: multiple, session = get_single_open_session_or_close_multiple( verified_number.domain, verified_number.owner_id) if multiple: send_sms_to_verified_number( verified_number, get_message(MSG_GENERIC_ERROR, verified_number)) return True if session: session.phone_number = verified_number.phone_number session.modified_time = datetime.utcnow() session.save() subevent = session.related_subevent subevent_id = subevent.id if subevent else None # Metadata to be applied to the inbound message inbound_metadata = MessageMetadata( workflow=session.workflow, reminder_id=session.reminder_id, xforms_session_couch_id=session._id, messaging_subevent_id=subevent_id, ) add_msg_tags(msg, inbound_metadata) msg.save() try: answer_next_question(verified_number, text, msg, session, subevent_id) except Exception: # Catch any touchforms errors log_sms_exception(msg) send_sms_to_verified_number( verified_number, get_message(MSG_TOUCHFORMS_DOWN, verified_number)) return True else: return False
def test_message_without_keyword(self): incoming('4444', '#update', 'TEST') self.assertEqual(self._get_last_outbound_message(), get_message(messages.MSG_UPDATE))
if text_args is not None: args = text_args else: args = split_args(text, survey_keyword) args = args[1:] keyword = survey_keyword.keyword.upper() error_occurred = False error_msg = None session = None app, module, form, error_occurred, error_code = get_app_module_form( domain, survey_keyword_action.app_id, survey_keyword_action.form_unique_id, logged_subevent) if error_occurred: error_msg = get_message(error_code, verified_number) clean_up_and_send_response(msg, contact, session, error_occurred, error_msg, verified_number, send_response, logged_event, logged_subevent) return False session, responses, error_occurred, error_code = start_session_for_structured_sms( domain, contact, verified_number, app, form, case_id, keyword, logged_subevent) if error_occurred: error_msg = get_message(error_code, verified_number) clean_up_and_send_response(msg, contact, session, error_occurred, error_msg, verified_number, send_response, logged_event, logged_subevent) return False
def process_incoming(msg): v, has_domain_two_way_scope = get_inbound_phone_entry(msg) if v: if any_migrations_in_progress(v.domain): raise DelayProcessing() msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.location_id = get_location_id_by_verified_number(v) msg.save() elif msg.domain_scope: if any_migrations_in_progress(msg.domain_scope): raise DelayProcessing() msg.domain = msg.domain_scope msg.save() opt_in_keywords, opt_out_keywords, pass_through_opt_in_keywords = get_opt_keywords(msg) domain = v.domain if v else None if is_opt_message(msg.text, opt_out_keywords): if PhoneBlacklist.opt_out_sms(msg.phone_number, domain=domain): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],)) if v: send_sms_to_verified_number(v, text, metadata=metadata) elif msg.backend_id: send_sms_with_backend(msg.domain, msg.phone_number, text, msg.backend_id, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords): if PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],)) if v: send_sms_to_verified_number(v, text) elif msg.backend_id: send_sms_with_backend(msg.domain, msg.phone_number, text, msg.backend_id) else: send_sms(msg.domain, None, msg.phone_number, text) else: if is_opt_message(msg.text, pass_through_opt_in_keywords): # Opt the phone number in, and then process the message normally PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain) handled = False is_two_way = v is not None and v.is_two_way if msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS): handled = load_and_call(settings.CUSTOM_SMS_HANDLERS, v, msg.text, msg) if not handled and v and v.pending_verification: from . import verify handled = verify.process_verification(v, msg, create_subevent_for_inbound=not has_domain_two_way_scope) if ( not handled and (is_two_way or has_domain_two_way_scope) and is_contact_active(v.domain, v.owner_doc_type, v.owner_id) ): handled = load_and_call(settings.SMS_HANDLERS, v, msg.text, msg) if not handled and not is_two_way: handled = process_pre_registration(msg) if not handled: handled = process_sms_registration(msg) # If the sms queue is enabled, then the billable gets created in remove_from_queue() if ( not settings.SMS_QUEUE_ENABLED and msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS) ): create_billable_for_sms(msg)
def process_incoming(msg): v = PhoneNumber.by_phone(msg.phone_number, include_pending=True) if v: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.location_id = get_location_id_by_verified_number(v) msg.save() elif msg.domain_scope: msg.domain = msg.domain_scope msg.save() can_receive_sms = PhoneBlacklist.can_receive_sms(msg.phone_number) opt_in_keywords, opt_out_keywords = get_opt_keywords(msg) domain = v.domain if v else None if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms: if PhoneBlacklist.opt_out_sms(msg.phone_number, domain=domain): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0], )) if v: send_sms_to_verified_number(v, text, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms: if PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0], )) if v: send_sms_to_verified_number(v, text) else: send_sms(msg.domain, None, msg.phone_number, text) else: handled = False is_verified = v is not None and v.verified if msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS): handled = load_and_call(settings.CUSTOM_SMS_HANDLERS, v, msg.text, msg) if not handled and is_verified and is_contact_active( v.domain, v.owner_doc_type, v.owner_id): handled = load_and_call(settings.SMS_HANDLERS, v, msg.text, msg) if not handled and not is_verified: handled = process_pre_registration(msg) if not handled: handled = process_sms_registration(msg) if not handled: import verify verify.process_verification(v, msg) # If the sms queue is enabled, then the billable gets created in remove_from_queue() if (not settings.SMS_QUEUE_ENABLED and msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS)): create_billable_for_sms(msg)
def process_incoming(msg): sms_load_counter("inbound", msg.domain)() v, has_domain_two_way_scope = get_inbound_phone_entry(msg) if v: if any_migrations_in_progress(v.domain): raise DelayProcessing() msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.location_id = get_location_id_by_verified_number(v) msg.save() elif msg.domain_scope: if any_migrations_in_progress(msg.domain_scope): raise DelayProcessing() msg.domain = msg.domain_scope msg.save() opt_in_keywords, opt_out_keywords, pass_through_opt_in_keywords = get_opt_keywords( msg) domain = v.domain if v else None if is_opt_message(msg.text, opt_out_keywords): if PhoneBlacklist.opt_out_sms(msg.phone_number, domain=domain): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0], )) if v: send_sms_to_verified_number(v, text, metadata=metadata) elif msg.backend_id: send_sms_with_backend(msg.domain, msg.phone_number, text, msg.backend_id, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords): if PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0], )) if v: send_sms_to_verified_number(v, text) elif msg.backend_id: send_sms_with_backend(msg.domain, msg.phone_number, text, msg.backend_id) else: send_sms(msg.domain, None, msg.phone_number, text) else: if is_opt_message(msg.text, pass_through_opt_in_keywords): # Opt the phone number in, and then process the message normally PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain) handled = False is_two_way = v is not None and v.is_two_way if msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS): if v and v.pending_verification: from . import verify handled = verify.process_verification( v, msg, create_subevent_for_inbound=not has_domain_two_way_scope) if ((is_two_way or has_domain_two_way_scope) and is_contact_active( v.domain, v.owner_doc_type, v.owner_id)): handled = load_and_call(settings.SMS_HANDLERS, v, msg.text, msg) if not handled and not is_two_way: handled = process_pre_registration(msg) if not handled: handled = process_sms_registration(msg) # If the sms queue is enabled, then the billable gets created in remove_from_queue() if (not settings.SMS_QUEUE_ENABLED and msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS)): create_billable_for_sms(msg)
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: 1) Select "Enable Mobile Worker Registration via SMS" in project settings. 2) Text in "join <domain> worker <username>", where <domain> is the domain to join and <username> is the requested username. If the username doesn't exist it will be created, otherwise the registration will error. If the username argument is not specified, the username will be the mobile 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. """ 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 "" keyword4 = text_words[3] if len(text_words) > 3 else "" cleaned_phone_number = strip_plus(msg.phone_number) if is_registration_text(msg.text) 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: if keyword4 != '': username = keyword4 else: username = cleaned_phone_number try: user_data = {} invitation = SelfRegistrationInvitation.by_phone(msg.phone_number) if invitation: invitation.completed() user_data = invitation.custom_user_data username = process_username(username, domain) password = random_password() new_user = CommCareUser.create(domain.name, username, password, user_data=user_data) new_user.add_phone_number(cleaned_phone_number) new_user.save_verified_number(domain.name, cleaned_phone_number, True, None) new_user.save() registration_processed = True if domain.enable_registration_welcome_sms_for_mobile_worker: send_sms(domain.name, None, cleaned_phone_number, get_message(MSG_REGISTRATION_WELCOME_MOBILE_WORKER, domain=domain.name)) except ValidationError as e: send_sms(domain.name, None, cleaned_phone_number, e.messages[0]) 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=cleaned_phone_number, contact_phone_number_is_verified="1", owner_id=domain.sms_case_registration_owner_id, ) registration_processed = True if domain.enable_registration_welcome_sms_for_case: send_sms(domain.name, None, cleaned_phone_number, get_message(MSG_REGISTRATION_WELCOME_CASE, domain=domain.name)) msg.domain = domain.name msg.save() return registration_processed
def test_message_with_invalid_site_code(self): incoming('4444', '#update location notexists', 'TEST') self.assertEqual( self._get_last_outbound_message(), get_message(messages.MSG_UPDATE_LOCATION_SITE_CODE_NOT_FOUND, context=['notexists']) )
def test_message_with_invalid_site_code(self): incoming('4444', '#update location notexists', 'TEST') self.assertEqual( self._get_last_outbound_message(), get_message(messages.MSG_UPDATE_LOCATION_SITE_CODE_NOT_FOUND, context=['notexists']))
def test_valid_message(self): incoming('4444', '#update location site_code', 'TEST') self.assertEqual(self._get_last_outbound_message(), get_message(messages.MSG_UPDATE_LOCATION_SUCCESS)) user = CommCareUser.get(docid=self.user.get_id) self.assertEqual(user.location_id, self.location.get_id)
def test_message_without_site_code(self): incoming('4444', '#update location', 'TEST') self.assertEqual(self._get_last_outbound_message(), get_message(messages.MSG_UPDATE_LOCATION_SYNTAX))
def test_with_invalid_action(self): incoming('4444', '#update notexists', 'TEST') self.assertEqual(self._get_last_outbound_message(), get_message(messages.MSG_UPDATE_UNRECOGNIZED_ACTION))
def global_keyword_unknown(verified_number, text, msg, text_words, open_sessions): message = get_message(MSG_UNKNOWN_GLOBAL_KEYWORD, verified_number, (text_words[0], )) send_sms_to_verified_number(verified_number, message) return True
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: 1) Select "Enable Mobile Worker Registration via SMS" in project settings. 2) Text in "join <domain> worker <username>", where <domain> is the domain to join and <username> is the requested username. If the username doesn't exist it will be created, otherwise the registration will error. If the username argument is not specified, the username will be the mobile 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. """ 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 "" keyword4 = text_words[3] if len(text_words) > 3 else "" cleaned_phone_number = strip_plus(msg.phone_number) if is_registration_text(msg.text) and keyword2 != "": domain_name = keyword2 if any_migrations_in_progress(domain_name): raise DelayProcessing() domain_obj = Domain.get_by_name(domain_name, strict=True) if domain_obj is not None: if domain_has_privilege(domain_obj, privileges.INBOUND_SMS): if (keyword3 in REGISTRATION_MOBILE_WORKER_KEYWORDS and domain_obj.sms_mobile_worker_registration_enabled): if keyword4 != '': username = keyword4 else: username = cleaned_phone_number try: user_data = {} invitation = SelfRegistrationInvitation.by_phone( msg.phone_number) if invitation: invitation.completed() user_data = invitation.custom_user_data username = process_username(username, domain_obj) password = random_password() new_user = CommCareUser.create(domain_obj.name, username, password, user_data=user_data) new_user.add_phone_number(cleaned_phone_number) new_user.save() entry = new_user.get_or_create_phone_entry( cleaned_phone_number) entry.set_two_way() entry.set_verified() entry.save() registration_processed = True if domain_obj.enable_registration_welcome_sms_for_mobile_worker: send_sms( domain_obj.name, None, cleaned_phone_number, get_message( MSG_REGISTRATION_WELCOME_MOBILE_WORKER, domain=domain_obj.name)) except ValidationError as e: send_sms(domain_obj.name, None, cleaned_phone_number, e.messages[0]) elif domain_obj.sms_case_registration_enabled: register_sms_contact( domain=domain_obj.name, case_type=domain_obj.sms_case_registration_type, case_name="unknown", user_id=domain_obj.sms_case_registration_user_id, contact_phone_number=cleaned_phone_number, contact_phone_number_is_verified="1", owner_id=domain_obj.sms_case_registration_owner_id, ) registration_processed = True if domain_obj.enable_registration_welcome_sms_for_case: send_sms( domain_obj.name, None, cleaned_phone_number, get_message(MSG_REGISTRATION_WELCOME_CASE, domain=domain_obj.name)) msg.domain = domain_obj.name msg.save() return registration_processed
def validate_answer(event, text, verified_number): text = text.strip() upper_text = text.upper() valid = False error_msg = "" if text == "" and event._dict.get("required", False): return (False, text, get_message(MSG_FIELD_REQUIRED, verified_number)) # Validate select if event.datatype == "select": # Try to match on phrase (i.e., "Yes" or "No") choices = format_choices(event._dict["choices"]) if upper_text in choices: text = str(choices[upper_text]) valid = True else: try: answer = int(text) if answer >= 1 and answer <= len(event._dict["choices"]): valid = True else: error_msg = get_message(MSG_CHOICE_OUT_OF_RANGE, verified_number) except ValueError: error_msg = get_message(MSG_INVALID_CHOICE, verified_number) # Validate multiselect elif event.datatype == "multiselect": choices = format_choices(event._dict["choices"]) max_index = len(event._dict["choices"]) proposed_answers = text.split() final_answers = {} try: for answer in proposed_answers: upper_answer = answer.upper() if upper_answer in choices: final_answers[str(choices[upper_answer])] = "" else: int_answer = int(answer) assert int_answer >= 1 and int_answer <= max_index final_answers[str(int_answer)] = "" text = " ".join(final_answers) valid = True except Exception: error_msg = get_message(MSG_INVALID_CHOICE, verified_number) # Validate int elif event.datatype == "int": try: value = int(text) if value >= -2147483648 and value <= 2147483647: valid = True else: error_msg = get_message(MSG_INVALID_INT_RANGE, verified_number) except ValueError: error_msg = get_message(MSG_INVALID_INT, verified_number) # Validate float elif event.datatype == "float": try: float(text) valid = True except ValueError: error_msg = get_message(MSG_INVALID_FLOAT, verified_number) # Validate longint elif event.datatype == "longint": try: int(text) valid = True except ValueError: error_msg = get_message(MSG_INVALID_LONG, verified_number) # Validate date (Format: specified by Domain.sms_survey_date_format, default: YYYYMMDD) elif event.datatype == "date": domain_obj = Domain.get_by_name(verified_number.domain) df = get_date_format(domain_obj.sms_survey_date_format) if df.is_valid(text): try: text = df.parse(text).strftime('%Y-%m-%d') valid = True except (ValueError, TypeError): pass if not valid: error_msg = get_message(MSG_INVALID_DATE, verified_number, context=(df.human_readable_format, )) # Validate time (Format: HHMM, 24-hour) elif event.datatype == "time": try: assert len(text) == 4 hour = int(text[0:2]) minute = int(text[2:]) assert hour >= 0 and hour <= 23 assert minute >= 0 and minute <= 59 text = "%s:%s" % (hour, str(minute).zfill(2)) valid = True except Exception: error_msg = get_message(MSG_INVALID_TIME, verified_number) # Other question types pass else: valid = True return (valid, text, error_msg)