def fallback_handler(v, text, msg): domain_obj = Domain.get_by_name(v.domain, strict=True) default_workflow_meta = MessageMetadata(workflow=WORKFLOW_DEFAULT, location_id=msg.location_id) if domain_obj.use_default_sms_response and domain_obj.default_sms_response: send_sms_to_verified_number(v, domain_obj.default_sms_response, metadata=default_workflow_meta) add_msg_tags(msg, default_workflow_meta) return True
def fallback_handler(verified_number, text, msg): domain_obj = Domain.get_by_name(verified_number.domain, strict=True) logged_event = MessagingEvent.create_event_for_adhoc_sms( verified_number.domain, recipient=verified_number.owner, content_type=MessagingEvent.CONTENT_SMS, source=MessagingEvent.SOURCE_UNRECOGNIZED) inbound_subevent = logged_event.create_subevent_for_single_sms( verified_number.owner_doc_type, verified_number.owner_id) inbound_meta = MessageMetadata(workflow=WORKFLOW_DEFAULT, messaging_subevent_id=inbound_subevent.pk) add_msg_tags(msg, inbound_meta) if domain_obj.use_default_sms_response and domain_obj.default_sms_response: outbound_subevent = logged_event.create_subevent_for_single_sms( verified_number.owner_doc_type, verified_number.owner_id) outbound_meta = MessageMetadata( workflow=WORKFLOW_DEFAULT, location_id=msg.location_id, messaging_subevent_id=outbound_subevent.pk) send_sms_to_verified_number(verified_number, domain_obj.default_sms_response, metadata=outbound_meta) outbound_subevent.completed() inbound_subevent.completed() logged_event.completed() return True
def form_session_handler(v, 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. """ multiple, session = get_single_open_session_or_close_multiple(v.domain, v.owner_id) if multiple: send_sms_to_verified_number(v, get_message(MSG_MULTIPLE_SESSIONS, v)) return True if session: # 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, ) add_msg_tags(msg, inbound_metadata) try: answer_next_question(v, text, msg, session) except Exception: # Catch any touchforms errors log_sms_exception(msg) send_sms_to_verified_number(v, get_message(MSG_TOUCHFORMS_DOWN, v)) return True else: return False
def forward_sms(msg, domain, verified_number, text, backend_id): logged_event = MessagingEvent.create_event_for_adhoc_sms( domain, recipient=verified_number.owner, content_type=MessagingEvent.CONTENT_SMS, source=MessagingEvent.SOURCE_FORWARDED) inbound_subevent = logged_event.create_subevent_for_single_sms( verified_number.owner_doc_type, verified_number.owner_id) inbound_meta = MessageMetadata(workflow=WORKFLOW_FORWARD, messaging_subevent_id=inbound_subevent.pk) add_msg_tags(msg, inbound_meta) outbound_subevent = logged_event.create_subevent_for_single_sms( verified_number.owner_doc_type, verified_number.owner_id) outbound_meta = MessageMetadata(workflow=WORKFLOW_FORWARD, messaging_subevent_id=outbound_subevent.pk) send_sms_with_backend(domain, verified_number.phone_number, text, backend_id, metadata=outbound_meta) outbound_subevent.completed() inbound_subevent.completed() logged_event.completed()
def fallback_handler(v, text, msg): domain_obj = Domain.get_by_name(v.domain, strict=True) logged_event = MessagingEvent.create_event_for_adhoc_sms( v.domain, recipient=v.owner, content_type=MessagingEvent.CONTENT_SMS, source=MessagingEvent.SOURCE_UNRECOGNIZED) inbound_subevent = logged_event.create_subevent_for_single_sms( v.owner_doc_type, v.owner_id) inbound_meta = MessageMetadata(workflow=WORKFLOW_DEFAULT, messaging_subevent_id=inbound_subevent.pk) add_msg_tags(msg, inbound_meta) if domain_obj.use_default_sms_response and domain_obj.default_sms_response: outbound_subevent = logged_event.create_subevent_for_single_sms( v.owner_doc_type, v.owner_id) outbound_meta = MessageMetadata(workflow=WORKFLOW_DEFAULT, location_id=msg.location_id, messaging_subevent_id=outbound_subevent.pk) send_sms_to_verified_number(v, domain_obj.default_sms_response, metadata=outbound_meta) outbound_subevent.completed() inbound_subevent.completed() logged_event.completed() return True
def form_session_handler(v, 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(v.owner_id): if toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled(v.domain): channel = get_channel_for_contact(v.owner_id, v.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 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( v.domain, v.owner_id) if multiple: send_sms_to_verified_number( v, get_message(MSG_MULTIPLE_SESSIONS, v)) return True if session: session.phone_number = v.phone_number session.modified_time = datetime.utcnow() session.save() # 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, ) add_msg_tags(msg, inbound_metadata) try: answer_next_question(v, text, msg, session) except Exception: # Catch any touchforms errors log_sms_exception(msg) send_sms_to_verified_number( v, get_message(MSG_TOUCHFORMS_DOWN, v)) return True else: return False
def add_keyword_metadata(msg, session): metadata = MessageMetadata( workflow=WORKFLOW_KEYWORD, xforms_session_couch_id=session._id if session else None, ) if msg: add_msg_tags(msg, metadata) return metadata
def handle_global_keywords(v, text, msg, text_words, open_sessions): global_keyword = text_words[0] global_keywords = { "#START": global_keyword_start, "#STOP": global_keyword_stop, "#CURRENT": global_keyword_current, } inbound_metadata = MessageMetadata(workflow=WORKFLOW_KEYWORD, ) add_msg_tags(msg, inbound_metadata) fcn = global_keywords.get(global_keyword, global_keyword_unknown) return fcn(v, text, msg, text_words, open_sessions)
def send_test_message(verified_number, text, metadata=None): msg = SMSLog(couch_recipient_doc_type=verified_number.owner_doc_type, couch_recipient=verified_number.owner_id, phone_number="+" + str(verified_number.phone_number), direction=OUTGOING, date=datetime.utcnow(), domain=verified_number.domain, text=text, processed=True, datetime_to_process=datetime.utcnow(), queued_timestamp=datetime.utcnow()) msg.save() add_msg_tags(msg, metadata) return True
def handle_global_keywords(v, text, msg, text_words, open_sessions): global_keyword = text_words[0] global_keywords = { "#START": global_keyword_start, "#STOP": global_keyword_stop, "#CURRENT": global_keyword_current, } inbound_metadata = MessageMetadata( workflow=WORKFLOW_KEYWORD, ) add_msg_tags(msg, inbound_metadata) fcn = global_keywords.get(global_keyword, global_keyword_unknown) return fcn(v, text, msg, text_words, open_sessions)
def send_test_message(verified_number, text, metadata=None): msg = SMSLog( couch_recipient_doc_type=verified_number.owner_doc_type, couch_recipient=verified_number.owner_id, phone_number="+" + str(verified_number.phone_number), direction=OUTGOING, date=datetime.utcnow(), domain=verified_number.domain, text=text, processed=True, datetime_to_process=datetime.utcnow(), queued_timestamp=datetime.utcnow(), ) msg.save() add_msg_tags(msg, metadata) return True
def handle_domain_keywords(v, text, msg, text_words, sessions): any_session_open = len(sessions) > 0 for survey_keyword in Keyword.get_by_domain(v.domain): args = split_args(text, survey_keyword) keyword = args[0].upper() if keyword == survey_keyword.keyword.upper(): if any_session_open and not survey_keyword.override_open_sessions: # We don't want to override any open sessions, so just pass and # let the form session handler handle the message return False elif not contact_can_use_keyword(v, survey_keyword): # The contact type is not allowed to invoke this keyword return False else: inbound_metadata = MessageMetadata(workflow=WORKFLOW_KEYWORD, ) add_msg_tags(msg, inbound_metadata) process_survey_keyword_actions(v, survey_keyword, text, msg) return True # No keywords matched, so pass the message onto the next handler return False
def process_survey_keyword_actions(verified_number, survey_keyword, text, msg): sender = verified_number.owner case = None args = split_args(text, survey_keyword) logged_event = MessagingEvent.create_from_keyword(survey_keyword, sender) # Log a messaging subevent for the incoming message subevent = logged_event.create_subevent_for_single_sms( msg.couch_recipient_doc_type, msg.couch_recipient, completed=True) add_msg_tags(msg, MessageMetadata(messaging_subevent_id=subevent.pk)) # Close any open sessions even if it's just an sms that we're # responding with. SQLXFormsSession.close_all_open_sms_sessions(verified_number.domain, verified_number.owner_id) if is_commcarecase(sender): case = sender args = args[1:]
def handle_domain_keywords(v, text, msg, text_words, sessions): any_session_open = len(sessions) > 0 for survey_keyword in SurveyKeyword.get_all(v.domain): args = split_args(text, survey_keyword) keyword = args[0].upper() if keyword == survey_keyword.keyword.upper(): if any_session_open and not survey_keyword.override_open_sessions: # We don't want to override any open sessions, so just pass and # let the form session handler handle the message return False elif not contact_can_use_keyword(v, survey_keyword): # The contact type is not allowed to invoke this keyword return False else: inbound_metadata = MessageMetadata( workflow=WORKFLOW_KEYWORD, ) add_msg_tags(msg, inbound_metadata) process_survey_keyword_actions(v, survey_keyword, text, msg) return True # No keywords matched, so pass the message onto the next handler return False
error_occurred = True error_msg = get_message(MSG_TOUCHFORMS_ERROR, verified_number) if session is not None: session = XFormsSession.get(session._id) if session.is_open: session.end(False) session.save() metadata = MessageMetadata( workflow=WORKFLOW_KEYWORD, xforms_session_couch_id=session._id if session else None, ) if msg: add_msg_tags(msg, metadata) if error_occurred and verified_number is not None and send_response: send_sms_to_verified_number(verified_number, error_msg, metadata=metadata) return not error_occurred def get_question_id(xformsresponse, xpath_arg=None): binding = xformsresponse.event._dict.get("binding", None) question_id = None if binding: if xpath_arg and (binding in xpath_arg): question_id = xpath_arg[binding] else: question_id = binding.split("/")[-1] return question_id
def process_survey_keyword_actions(verified_number, survey_keyword, text, msg): sender = verified_number.owner case = None args = split_args(text, survey_keyword) logged_event = MessagingEvent.create_from_keyword(survey_keyword, sender) # Log a messaging subevent for the incoming message subevent = logged_event.create_subevent_for_single_sms( msg.couch_recipient_doc_type, msg.couch_recipient, completed=True ) add_msg_tags(msg, MessageMetadata(messaging_subevent_id=subevent.pk)) # Close any open sessions even if it's just an sms that we're # responding with. SQLXFormsSession.close_all_open_sms_sessions(verified_number.domain, verified_number.owner_id) if is_commcarecase(sender): case = sender args = args[1:] elif isinstance(sender, CommCareUser): if keyword_uses_form_that_requires_case(survey_keyword): if len(args) > 1: external_id = args[1] case, matches = get_case_by_external_id(verified_number.domain, external_id, sender) if matches == 0: send_keyword_response(verified_number, MSG_CASE_NOT_FOUND, logged_event) logged_event.error(MessagingEvent.ERROR_CASE_EXTERNAL_ID_NOT_FOUND) return elif matches > 1: send_keyword_response(verified_number, MSG_MULTIPLE_CASES_FOUND, logged_event) logged_event.error(MessagingEvent.ERROR_MULTIPLE_CASES_WITH_EXTERNAL_ID_FOUND) return else: send_keyword_response(verified_number, MSG_MISSING_EXTERNAL_ID, logged_event) logged_event.error(MessagingEvent.ERROR_NO_EXTERNAL_ID_GIVEN) return args = args[2:] else: args = args[1:] def cmp_fcn(a1, a2): a1_ss = (a1.action == KeywordAction.ACTION_STRUCTURED_SMS) a2_ss = (a2.action == KeywordAction.ACTION_STRUCTURED_SMS) if a1_ss and a2_ss: return 0 elif a1_ss: return -1 elif a2_ss: return 1 else: return 0 if case: subevent.case_id = case.case_id subevent.save() # Process structured sms actions first actions = sorted(survey_keyword.keywordaction_set.all(), cmp=cmp_fcn) for survey_keyword_action in actions: if survey_keyword_action.recipient == KeywordAction.RECIPIENT_SENDER: contact = sender elif survey_keyword_action.recipient == KeywordAction.RECIPIENT_OWNER: if is_commcarecase(sender): contact = get_wrapped_owner(get_owner_id(sender)) else: contact = None elif survey_keyword_action.recipient == KeywordAction.RECIPIENT_USER_GROUP: try: contact = Group.get(survey_keyword_action.recipient_id) assert contact.doc_type == "Group" assert contact.domain == verified_number.domain except Exception: contact = None else: contact = None if contact is None: continue # contact can be either a user, case, group, or location if survey_keyword_action.action in (KeywordAction.ACTION_SMS, KeywordAction.ACTION_SMS_SURVEY): if isinstance(contact, Group): recipients = list(ScheduleInstance.expand_group(contact)) elif isinstance(contact, SQLLocation): recipients = list(ScheduleInstance.expand_location_ids(contact.domain, [contact.location_id])) else: recipients = [contact] recipient_is_sender = survey_keyword_action.recipient == KeywordAction.RECIPIENT_SENDER if survey_keyword_action.action == KeywordAction.ACTION_SMS: content = SMSContent(message={'*': survey_keyword_action.message_content}) content.set_context(case=case) elif survey_keyword_action.action == KeywordAction.ACTION_SMS_SURVEY: content = SMSSurveyContent( form_unique_id=survey_keyword_action.form_unique_id, expire_after=SQLXFormsSession.MAX_SESSION_LENGTH, ) content.set_context( case=case, critical_section_already_acquired=recipient_is_sender, ) else: raise ValueError("Unexpected action %s" % survey_keyword_action.action) for recipient in recipients: phone_entry = verified_number if recipient_is_sender else None content.send(recipient, logged_event, phone_entry=phone_entry) elif survey_keyword_action.action == KeywordAction.ACTION_STRUCTURED_SMS: res = handle_structured_sms(survey_keyword, survey_keyword_action, sender, verified_number, text, send_response=True, msg=msg, case=case, text_args=args, logged_event=logged_event) if not res: # If the structured sms processing wasn't successful, don't # process any of the other actions return logged_event.completed()
return 0 elif a1_ss: return -1 elif a2_ss: return 1 else: return 0 # Log a messaging subevent for the incoming message subevent = logged_event.create_subevent_for_single_sms( msg.couch_recipient_doc_type, msg.couch_recipient, case ) subevent.completed() add_msg_tags(msg, MessageMetadata(messaging_subevent_id=subevent.pk)) # Process structured sms actions first actions = sorted(survey_keyword.actions, cmp=cmp_fcn) for survey_keyword_action in actions: if survey_keyword_action.recipient == RECIPIENT_SENDER: contact = sender elif survey_keyword_action.recipient == RECIPIENT_OWNER: if sender.doc_type == "CommCareCase": contact = get_wrapped_owner(get_owner_id(sender)) else: contact = None elif survey_keyword_action.recipient == RECIPIENT_USER_GROUP: try: contact = Group.get(survey_keyword_action.recipient_id) assert contact.doc_type == "Group"
def process_survey_keyword_actions(verified_number, survey_keyword, text, msg): sender = verified_number.owner case = None args = split_args(text, survey_keyword) logged_event = MessagingEvent.create_from_keyword(survey_keyword, sender) # Log a messaging subevent for the incoming message subevent = logged_event.create_subevent_for_single_sms( msg.couch_recipient_doc_type, msg.couch_recipient ) subevent.completed() add_msg_tags(msg, MessageMetadata(messaging_subevent_id=subevent.pk)) # Close any open sessions even if it's just an sms that we're # responding with. SQLXFormsSession.close_all_open_sms_sessions(verified_number.domain, verified_number.owner_id) if sender.doc_type == "CommCareCase": case = sender args = args[1:] elif sender.doc_type == "CommCareUser": if keyword_uses_form_that_requires_case(survey_keyword): if len(args) > 1: external_id = args[1] case, matches = get_case_by_external_id(verified_number.domain, external_id, sender) if matches == 0: send_keyword_response(verified_number, MSG_CASE_NOT_FOUND, logged_event) logged_event.error(MessagingEvent.ERROR_CASE_EXTERNAL_ID_NOT_FOUND) return elif matches > 1: send_keyword_response(verified_number, MSG_MULTIPLE_CASES_FOUND, logged_event) logged_event.error(MessagingEvent.ERROR_MULTIPLE_CASES_WITH_EXTERNAL_ID_FOUND) return else: send_keyword_response(verified_number, MSG_MISSING_EXTERNAL_ID, logged_event) logged_event.error(MessagingEvent.ERROR_NO_EXTERNAL_ID_GIVEN) return args = args[2:] else: args = args[1:] def cmp_fcn(a1, a2): a1_ss = (a1.action == METHOD_STRUCTURED_SMS) a2_ss = (a2.action == METHOD_STRUCTURED_SMS) if a1_ss and a2_ss: return 0 elif a1_ss: return -1 elif a2_ss: return 1 else: return 0 if case: subevent.case_id = case.get_id subevent.save() # Process structured sms actions first actions = sorted(survey_keyword.actions, cmp=cmp_fcn) for survey_keyword_action in actions: if survey_keyword_action.recipient == RECIPIENT_SENDER: contact = sender elif survey_keyword_action.recipient == RECIPIENT_OWNER: if sender.doc_type == "CommCareCase": contact = get_wrapped_owner(get_owner_id(sender)) else: contact = None elif survey_keyword_action.recipient == RECIPIENT_USER_GROUP: try: contact = Group.get(survey_keyword_action.recipient_id) assert contact.doc_type == "Group" assert contact.domain == verified_number.domain except Exception: contact = None else: contact = None if contact is None: continue if survey_keyword_action.action == METHOD_SMS: create_immediate_reminder(contact, METHOD_SMS, reminder_type=REMINDER_TYPE_KEYWORD_INITIATED, message=survey_keyword_action.message_content, case=case, logged_event=logged_event) elif survey_keyword_action.action == METHOD_SMS_SURVEY: create_immediate_reminder(contact, METHOD_SMS_SURVEY, reminder_type=REMINDER_TYPE_KEYWORD_INITIATED, form_unique_id=survey_keyword_action.form_unique_id, case=case, logged_event=logged_event) elif survey_keyword_action.action == METHOD_STRUCTURED_SMS: res = handle_structured_sms(survey_keyword, survey_keyword_action, sender, verified_number, text, send_response=True, msg=msg, case=case, text_args=args, logged_event=logged_event) if not res: # If the structured sms processing wasn't successful, don't # process any of the other actions return logged_event.completed()
def handle_structured_sms(survey_keyword, survey_keyword_action, contact, verified_number, text, send_response=False, msg=None, case=None, text_args=None): domain = contact.domain contact_doc_type = contact.doc_type contact_id = contact._id 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 try: # Start the session app, module, form = get_form( survey_keyword_action.form_unique_id, include_app_module=True) if case: case_id = case._id elif contact_doc_type == "CommCareCase": case_id = contact_id else: case_id = None session, responses = start_session(domain, contact, app, module, form, case_id=case_id, yield_responses=True) session.workflow = WORKFLOW_KEYWORD session.save() assert len(responses) > 0, "There should be at least one response." first_question = responses[-1] if not is_form_complete(first_question): if survey_keyword_action.use_named_args: # Arguments in the sms are named xpath_answer = parse_structured_sms_named_args(args, survey_keyword_action, verified_number) _handle_structured_sms(domain, args, contact_id, session, first_question, verified_number, xpath_answer) else: # Arguments in the sms are not named; pass each argument to # each question in order _handle_structured_sms(domain, args, contact_id, session, first_question, verified_number) except StructuredSMSException as sse: error_occurred = True error_msg = "" if sse.xformsresponse and sse.xformsresponse.event: xpath_arg = None if survey_keyword_action.use_named_args: xpath_arg = \ {v: k for k, v in survey_keyword_action.named_args.items()} field_name = get_question_id(sse.xformsresponse, xpath_arg) error_msg = get_message(MSG_FIELD_DESCRIPTOR, verified_number, (field_name,)) error_msg = "%s%s" % (error_msg, sse.response_text) except Exception: notify_exception(None, message=("Could not process structured sms for" "contact %s, domain %s, keyword %s" % (contact_id, domain, keyword))) error_occurred = True error_msg = get_message(MSG_TOUCHFORMS_ERROR, verified_number) if session is not None: session = SQLXFormsSession.objects.get(couch_id=session._id) if session.is_open: session.end(False) session.save() metadata = MessageMetadata( workflow=WORKFLOW_KEYWORD, xforms_session_couch_id=session._id if session else None, ) if msg: add_msg_tags(msg, metadata) if error_occurred and verified_number is not None and send_response: send_sms_to_verified_number(verified_number, error_msg, metadata=metadata) return not error_occurred