예제 #1
0
파일: clients.py 프로젝트: songea/rapidpro
    def start_call(self, call, to, from_, status_callback):
        if not settings.SEND_CALLS:
            raise IVRException("SEND_CALLS set to False, skipping call start")

        channel = call.channel
        Contact.get_or_create(channel.org,
                              channel.created_by,
                              urns=[URN.from_tel(to)])

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = six.text_type(Flow.handle_call(call))

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint,
                         urlencode(
                             dict(channel=self.verboice_channel, address=to)))
        response = requests.post(url, data=payload, auth=self.auth).json()

        if 'call_id' not in response:
            raise IVRException(_('Verboice connection failed.'))

        # store the verboice call id in our IVRCall
        call.external_id = response['call_id']
        call.status = IVRCall.IN_PROGRESS
        call.save()
예제 #2
0
    def handle_direct_inbound(self, request, uuid, data):
        from warapidpro.types import WhatsAppDirectType
        channel = self.lookup_channel(WhatsAppDirectType.code, uuid)
        if not channel:
            error_msg = "Channel not found for id: %s" % (uuid, )
            logger.error(error_msg)
            return HttpResponse(error_msg, status=400)

        from_addr = data['from_addr']
        content = self.get_content(data)
        attachments = self.get_attachments(data)

        message = Msg.create_incoming(channel,
                                      URN.from_tel(from_addr),
                                      content,
                                      external_id=data['uuid'],
                                      attachments=attachments)
        response_body = {
            'message_id': message.pk,
        }

        request_body = request.body
        request_method = request.method
        request_path = request.get_full_path()

        event = HttpEvent(request_method, request_path, request_body, 201,
                          json.dumps(response_body))
        ChannelLog.log_message(message, 'Handled inbound message.', event)
        return JsonResponse(response_body, status=201)
예제 #3
0
파일: tests.py 프로젝트: ianjuma/rapidpro
    def create_contact(self, name=None, number=None, twitter=None, urn=None, is_test=False, **kwargs):
        """
        Create a contact in the master test org
        """
        urns = []
        if number:
            urns.append(URN.from_tel(number))
        if twitter:
            urns.append(URN.from_twitter(twitter))
        if urn:
            urns.append(urn)

        if not name and not urns:  # pragma: no cover
            raise ValueError("Need a name or URN to create a contact")

        kwargs['name'] = name
        kwargs['urns'] = urns
        kwargs['is_test'] = is_test

        if 'org' not in kwargs:
            kwargs['org'] = self.org
        if 'user' not in kwargs:
            kwargs['user'] = self.user

        return Contact.get_or_create(**kwargs)
예제 #4
0
    def parse_contacts(cls, org, json_obj):
        contacts = []
        for contact in json_obj.get(VariableContactAction.CONTACTS):
            name = contact.get(VariableContactAction.NAME, None)
            phone = contact.get(VariableContactAction.PHONE, None)
            contact_uuid = contact.get(VariableContactAction.UUID, None)

            urns = []
            for urn in contact.get(VariableContactAction.URNS, []):
                scheme = urn.get(VariableContactAction.SCHEME)
                path = urn.get(VariableContactAction.PATH)

                if scheme and path:
                    urns.append(URN.from_parts(scheme, path))

            if phone:  # pragma: needs cover
                urns.append(URN.from_tel(phone))

            contact = Contact.objects.filter(uuid=contact_uuid, org=org).first()

            if not contact:
                contact = Contact.get_or_create_by_urns(org, org.created_by, name=None, urns=urns)

                # if they don't have a name use the one in our action
                if name and not contact.name:  # pragma: needs cover
                    contact.name = name
                    contact.save(update_fields=["name"], handle_update=True)

            if contact:
                contacts.append(contact)

        return contacts
예제 #5
0
파일: base.py 프로젝트: tangibleai/rapidpro
    def create_contact(self,
                       name=None,
                       *,
                       language=None,
                       phone=None,
                       urns=None,
                       fields=None,
                       org=None,
                       user=None,
                       last_seen_on=None):
        """
        Create a new contact
        """

        org = org or self.org
        user = user or self.user
        urns = [URN.from_tel(phone)] if phone else urns

        return create_contact_locally(org,
                                      user,
                                      name,
                                      language,
                                      urns or [],
                                      fields or {},
                                      group_uuids=[],
                                      last_seen_on=last_seen_on)
예제 #6
0
파일: base.py 프로젝트: iamfip/rapidpro
    def create_contact(self,
                       name=None,
                       number=None,
                       twitter=None,
                       urn=None,
                       fields=None,
                       **kwargs):
        """
        Create a contact in the master test org
        """

        org = kwargs.pop("org", None) or self.org
        user = kwargs.pop("user", None) or self.user

        urns = []
        if number:
            urns.append(URN.from_tel(number))
        if twitter:
            urns.append(URN.from_twitter(twitter))
        if urn:
            urns.append(urn)

        assert name or urns, "contact should have a name or a contact"

        kwargs["name"] = name
        kwargs["urns"] = urns

        contact = Contact.get_or_create_by_urns(org, user, **kwargs)

        if fields:
            update_fields_locally(user, contact, fields)

        return contact
예제 #7
0
    def start_call(self, call, to, from_, status_callback):
        if not settings.SEND_CALLS:
            raise ValueError("SEND_CALLS set to False, skipping call start")

        channel = call.channel
        Contact.get_or_create(channel.org, URN.from_tel(to), channel)

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = str(Flow.handle_call(call))

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint, urlencode(dict(channel=self.verboice_channel, address=to)))
        response = requests.post(url, data=payload, auth=self.auth).json()

        if "call_id" not in response:
            call.status = IVRCall.FAILED
            call.save()

            raise IVRException(_("Verboice connection failed."))

        # store the verboice call id in our IVRCall
        call.external_id = response["call_id"]

        # the call was successfully sent to the IVR provider
        call.status = IVRCall.WIRED
        call.save()
예제 #8
0
파일: base.py 프로젝트: mxabierto/rapidpro
    def create_contact(self, name=None, number=None, twitter=None, twitterid=None, urn=None, is_test=False, **kwargs):
        """
        Create a contact in the master test org
        """
        urns = []
        if number:
            urns.append(URN.from_tel(number))
        if twitter:
            urns.append(URN.from_twitter(twitter))
        if twitterid:
            urns.append(URN.from_twitterid(twitterid))
        if urn:
            urns.append(urn)

        if not name and not urns:  # pragma: no cover
            raise ValueError("Need a name or URN to create a contact")

        kwargs["name"] = name
        kwargs["urns"] = urns
        kwargs["is_test"] = is_test

        if "org" not in kwargs:
            kwargs["org"] = self.org
        if "user" not in kwargs:
            kwargs["user"] = self.user

        return Contact.get_or_create_by_urns(**kwargs)
예제 #9
0
    def create_contact(self,
                       name=None,
                       number=None,
                       twitter=None,
                       twitterid=None,
                       urn=None,
                       **kwargs):
        """
        Create a contact in the master test org
        """
        urns = []
        if number:
            urns.append(URN.from_tel(number))
        if twitter:
            urns.append(URN.from_twitter(twitter))
        if twitterid:
            urns.append(URN.from_twitterid(twitterid))
        if urn:
            urns.append(urn)

        if not name and not urns:  # pragma: no cover
            raise ValueError("Need a name or URN to create a contact")

        kwargs["name"] = name
        kwargs["urns"] = urns

        if "org" not in kwargs:
            kwargs["org"] = self.org
        if "user" not in kwargs:
            kwargs["user"] = self.user

        return Contact.get_or_create_by_urns(**kwargs)
예제 #10
0
    def create_contact(self,
                       name=None,
                       number=None,
                       twitter=None,
                       urn=None,
                       is_test=False,
                       **kwargs):
        """
        Create a contact in the master test org
        """
        urns = []
        if number:
            urns.append(URN.from_tel(number))
        if twitter:
            urns.append(URN.from_twitter(twitter))
        if urn:
            urns.append(urn)

        if not name and not urns:  # pragma: no cover
            raise ValueError("Need a name or URN to create a contact")

        kwargs['name'] = name
        kwargs['urns'] = urns
        kwargs['is_test'] = is_test

        if 'org' not in kwargs:
            kwargs['org'] = self.org
        if 'user' not in kwargs:
            kwargs['user'] = self.user

        return Contact.get_or_create(**kwargs)
예제 #11
0
    def to_internal_value(self, data):
        if isinstance(data, str):
            return [URN.from_tel(data)]

        elif isinstance(data, list):
            if len(data) > 100:
                raise serializers.ValidationError("You can only specify up to 100 numbers at a time.")

            urns = []
            for phone in data:
                if not isinstance(phone, str):  # pragma: no cover
                    raise serializers.ValidationError("Invalid phone: %s" % str(phone))
                urns.append(URN.from_tel(phone))

            return urns
        else:
            raise serializers.ValidationError("Invalid phone: %s" % data)
예제 #12
0
    def get_or_create_contact(self, urn):
        if ':' not in urn:
            urn = URN.from_tel(urn)  # assume phone number

        return Contact.get_or_create(self.org,
                                     self.user,
                                     name=None,
                                     urns=[urn])
예제 #13
0
    def to_internal_value(self, data):
        if isinstance(data, six.string_types):
            return [URN.from_tel(data)]

        elif isinstance(data, list):
            if len(data) > 100:
                raise serializers.ValidationError("You can only specify up to 100 numbers at a time.")

            urns = []
            for phone in data:
                if not isinstance(phone, six.string_types):  # pragma: no cover
                    raise serializers.ValidationError("Invalid phone: %s" % str(phone))
                urns.append(URN.from_tel(phone))

            return urns
        else:
            raise serializers.ValidationError("Invalid phone: %s" % data)
예제 #14
0
    def _create_contact_batch(self, batch):
        """
        Bulk creates a batch of contacts from flat representations
        """
        for c in batch:
            c["object"] = Contact(
                org=c["org"],
                name=c["name"],
                language=c["language"],
                is_stopped=c["is_stopped"],
                is_blocked=c["is_blocked"],
                is_active=c["is_active"],
                created_by=c["user"],
                created_on=c["created_on"],
                modified_by=c["user"],
                modified_on=c["modified_on"],
                fields=c["fields_as_json"],
            )
        Contact.objects.bulk_create([c["object"] for c in batch])

        # now that contacts have pks, bulk create the actual URN, value and group membership objects
        batch_urns = []
        batch_memberships = []

        for c in batch:
            org = c["org"]
            c["urns"] = []

            if c["tel"]:
                c["urns"].append(
                    ContactURN(
                        org=org,
                        contact=c["object"],
                        priority=50,
                        scheme=TEL_SCHEME,
                        path=c["tel"],
                        identity=URN.from_tel(c["tel"]),
                    )
                )
            if c["twitter"]:
                c["urns"].append(
                    ContactURN(
                        org=org,
                        contact=c["object"],
                        priority=50,
                        scheme=TWITTER_SCHEME,
                        path=c["twitter"],
                        identity=URN.from_twitter(c["twitter"]),
                    )
                )
            for g in c["groups"]:
                batch_memberships.append(ContactGroup.contacts.through(contact=c["object"], contactgroup=g))

            batch_urns += c["urns"]

        ContactURN.objects.bulk_create(batch_urns)
        ContactGroup.contacts.through.objects.bulk_create(batch_memberships)
예제 #15
0
    def _create_contact_batch(self, batch):
        """
        Bulk creates a batch of contacts from flat representations
        """
        for c in batch:
            c["object"] = Contact(
                org=c["org"],
                name=c["name"],
                language=c["language"],
                is_stopped=c["is_stopped"],
                is_blocked=c["is_blocked"],
                is_active=c["is_active"],
                created_by=c["user"],
                created_on=c["created_on"],
                modified_by=c["user"],
                modified_on=c["modified_on"],
                fields=c["fields_as_json"],
            )
        Contact.objects.bulk_create([c["object"] for c in batch])

        # now that contacts have pks, bulk create the actual URN, value and group membership objects
        batch_urns = []
        batch_memberships = []

        for c in batch:
            org = c["org"]
            c["urns"] = []

            if c["tel"]:
                c["urns"].append(
                    ContactURN(
                        org=org,
                        contact=c["object"],
                        priority=50,
                        scheme=TEL_SCHEME,
                        path=c["tel"],
                        identity=URN.from_tel(c["tel"]),
                    )
                )
            if c["twitter"]:
                c["urns"].append(
                    ContactURN(
                        org=org,
                        contact=c["object"],
                        priority=50,
                        scheme=TWITTER_SCHEME,
                        path=c["twitter"],
                        identity=URN.from_twitter(c["twitter"]),
                    )
                )
            for g in c["groups"]:
                batch_memberships.append(ContactGroup.contacts.through(contact=c["object"], contactgroup=g))

            batch_urns += c["urns"]

        ContactURN.objects.bulk_create(batch_urns)
        ContactGroup.contacts.through.objects.bulk_create(batch_memberships)
예제 #16
0
    def validate_phone(self, value):
        if value:
            try:
                normalized = phonenumbers.parse(value, None)
                if not phonenumbers.is_possible_number(normalized):
                    raise serializers.ValidationError("Invalid phone number: '%s'" % value)
            except Exception:
                raise serializers.ValidationError("Invalid phone number: '%s'" % value)

            e164_number = phonenumbers.format_number(normalized, phonenumbers.PhoneNumberFormat.E164)
            self.parsed_urns = [URN.from_tel(e164_number)]
        return value
예제 #17
0
    def validate_phone(self, value):
        if value:
            try:
                normalized = phonenumbers.parse(value, None)
                if not phonenumbers.is_possible_number(normalized):
                    raise serializers.ValidationError("Invalid phone number: '%s'" % value)
            except Exception:
                raise serializers.ValidationError("Invalid phone number: '%s'" % value)

            e164_number = phonenumbers.format_number(normalized, phonenumbers.PhoneNumberFormat.E164)
            self.parsed_urns = [URN.from_tel(e164_number)]
        return value
예제 #18
0
    def _create_contact_batch(self, batch):
        """
        Bulk creates a batch of contacts from flat representations
        """
        for c in batch:
            c['object'] = Contact(org=c['org'], name=c['name'], language=c['language'],
                                  is_stopped=c['is_stopped'], is_blocked=c['is_blocked'],
                                  is_active=c['is_active'],
                                  created_by=c['user'], created_on=c['created_on'],
                                  modified_by=c['user'], modified_on=c['modified_on'])
        Contact.objects.bulk_create([c['object'] for c in batch])

        # now that contacts have pks, bulk create the actual URN, value and group membership objects
        batch_urns = []
        batch_values = []
        batch_memberships = []

        for c in batch:
            org = c['org']
            c['urns'] = []

            if c['tel']:
                c['urns'].append(ContactURN(org=org, contact=c['object'], priority=50, scheme=TEL_SCHEME,
                                            path=c['tel'], urn=URN.from_tel(c['tel'])))
            if c['twitter']:
                c['urns'].append(ContactURN(org=org, contact=c['object'], priority=50, scheme=TWITTER_SCHEME,
                                            path=c['twitter'], urn=URN.from_twitter(c['twitter'])))
            if c['gender']:
                batch_values.append(Value(org=org, contact=c['object'], contact_field=org.cache['fields']['gender'],
                                          string_value=c['gender']))
            if c['age']:
                batch_values.append(Value(org=org, contact=c['object'], contact_field=org.cache['fields']['age'],
                                          string_value=str(c['age']), decimal_value=c['age']))
            if c['joined']:
                batch_values.append(Value(org=org, contact=c['object'], contact_field=org.cache['fields']['joined'],
                                          string_value=datetime_to_str(c['joined']), datetime_value=c['joined']))
            if c['ward']:
                batch_values.append(Value(org=org, contact=c['object'], contact_field=org.cache['fields']['ward'],
                                          string_value=c['ward'].name, location_value=c['ward']))
            if c['district']:
                batch_values.append(Value(org=org, contact=c['object'], contact_field=org.cache['fields']['district'],
                                          string_value=c['district'].name, location_value=c['district']))
            if c['state']:
                batch_values.append(Value(org=org, contact=c['object'], contact_field=org.cache['fields']['state'],
                                          string_value=c['state'].name, location_value=c['state']))
            for g in c['groups']:
                batch_memberships.append(ContactGroup.contacts.through(contact=c['object'], contactgroup=g))

            batch_urns += c['urns']

        ContactURN.objects.bulk_create(batch_urns)
        Value.objects.bulk_create(batch_values)
        ContactGroup.contacts.through.objects.bulk_create(batch_memberships)
예제 #19
0
파일: test_db.py 프로젝트: udomobi/push
    def _create_contact_batch(self, batch):
        """
        Bulk creates a batch of contacts from flat representations
        """
        for c in batch:
            c['object'] = Contact(org=c['org'],
                                  name=c['name'],
                                  language=c['language'],
                                  is_stopped=c['is_stopped'],
                                  is_blocked=c['is_blocked'],
                                  is_active=c['is_active'],
                                  created_by=c['user'],
                                  created_on=c['created_on'],
                                  modified_by=c['user'],
                                  modified_on=c['modified_on'],
                                  fields=c['fields_as_json'])
        Contact.objects.bulk_create([c['object'] for c in batch])

        # now that contacts have pks, bulk create the actual URN, value and group membership objects
        batch_urns = []
        batch_memberships = []

        for c in batch:
            org = c['org']
            c['urns'] = []

            if c['tel']:
                c['urns'].append(
                    ContactURN(org=org,
                               contact=c['object'],
                               priority=50,
                               scheme=TEL_SCHEME,
                               path=c['tel'],
                               identity=URN.from_tel(c['tel'])))
            if c['twitter']:
                c['urns'].append(
                    ContactURN(org=org,
                               contact=c['object'],
                               priority=50,
                               scheme=TWITTER_SCHEME,
                               path=c['twitter'],
                               identity=URN.from_twitter(c['twitter'])))
            for g in c['groups']:
                batch_memberships.append(
                    ContactGroup.contacts.through(contact=c['object'],
                                                  contactgroup=g))

            batch_urns += c['urns']

        ContactURN.objects.bulk_create(batch_urns)
        ContactGroup.contacts.through.objects.bulk_create(batch_memberships)
예제 #20
0
파일: views.py 프로젝트: tma-comms/rapidpro
        def form_valid(self, *args, **kwargs):
            data = self.form.cleaned_data
            handled = Msg.create_incoming(data['channel'], URN.from_tel(data['urn']), data['text'],
                                          user=self.request.user)

            kwargs = self.get_form_kwargs()
            kwargs['initial'] = data
            next_form = TestMessageForm(**kwargs)

            context = self.get_context_data()
            context['handled'] = handled
            context['form'] = next_form
            context['responses'] = handled.responses.all()

            # passing a minimal base template and a simple Context (instead of RequestContext) helps us
            # minimize number of other queries, allowing us to more easily measure queries per request
            context['base_template'] = 'msgs/msg_test_frame.html'
            return self.render_to_response(Context(context))
예제 #21
0
파일: views.py 프로젝트: ewheeler/rapidpro
        def form_valid(self, *args, **kwargs):
            data = self.form.cleaned_data
            handled = Msg.create_incoming(data['channel'], URN.from_tel(data['urn']), data['text'],
                                          user=self.request.user)

            kwargs = self.get_form_kwargs()
            kwargs['initial'] = data
            next_form = TestMessageForm(**kwargs)

            context = self.get_context_data()
            context['handled'] = handled
            context['form'] = next_form
            context['responses'] = handled.responses.all()

            # passing a minimal base template and a simple Context (instead of RequestContext) helps us
            # minimize number of other queries, allowing us to more easily measure queries per request
            context['base_template'] = 'msgs/msg_test_frame.html'
            return self.render_to_response(Context(context))
예제 #22
0
파일: clients.py 프로젝트: ianjuma/rapidpro
    def start_call(self, call, to, from_, status_callback):

        channel = call.channel
        Contact.get_or_create(channel.org, channel.created_by, urns=[URN.from_tel(to)])

        # Verboice differs from Twilio in that they expect the first block of twiml up front
        payload = unicode(Flow.handle_call(call, {}))

        # now we can post that to verboice
        url = "%s?%s" % (self.endpoint, urlencode(dict(channel=self.verboice_channel, address=to)))
        response = requests.post(url, data=payload, auth=self.auth).json()

        if 'call_id' not in response:
            raise IVRException(_('Verboice connection failed.'))

        # store the verboice call id in our IVRCall
        call.external_id = response['call_id']
        call.status = IN_PROGRESS
        call.save()
예제 #23
0
        def form_valid(self, *args, **kwargs):  # pragma: no cover
            data = self.form.cleaned_data
            handled = Msg.create_incoming(
                data["channel"], URN.from_tel(data["urn"]), data["text"], user=self.request.user
            )

            kwargs = self.get_form_kwargs()
            kwargs["initial"] = data
            next_form = TestMessageForm(**kwargs)

            context = self.get_context_data()
            context["handled"] = handled
            context["form"] = next_form
            context["responses"] = handled.responses.all()

            # passing a minimal base template and a simple Context (instead of RequestContext) helps us
            # minimize number of other queries, allowing us to more easily measure queries per request
            context["base_template"] = "msgs/msg_test_frame.html"
            return self.render_to_response(context)
예제 #24
0
파일: views.py 프로젝트: azizur77/rapidpro
        def form_valid(self, *args, **kwargs):  # pragma: no cover
            data = self.form.cleaned_data
            handled = Msg.create_incoming(data["channel"],
                                          URN.from_tel(data["urn"]),
                                          data["text"],
                                          user=self.request.user)

            kwargs = self.get_form_kwargs()
            kwargs["initial"] = data
            next_form = TestMessageForm(**kwargs)

            context = self.get_context_data()
            context["handled"] = handled
            context["form"] = next_form
            context["responses"] = handled.responses.all()

            # passing a minimal base template and a simple Context (instead of RequestContext) helps us
            # minimize number of other queries, allowing us to more easily measure queries per request
            context["base_template"] = "msgs/msg_test_frame.html"
            return self.render_to_response(context)
예제 #25
0
    def handle_group_inbound(self, request, uuid, data):
        from warapidpro.types import WhatsAppGroupType
        channel = self.lookup_channel(WhatsAppGroupType.code, uuid)
        if not channel:
            error_msg = "Channel not found for id: %s" % (uuid, )
            logger.error(error_msg)
            return HttpResponse(error_msg, status=400)

        from_addr = data['from_addr']
        content = self.get_content(data)
        attachments = self.get_attachments(data)
        group_uuid = data.get('group', {}).get('uuid')

        # The group webhook receives messages for all groups,
        # only grab the message if it's a group we're a channel for.
        if channel.config_json()['group_uuid'] != group_uuid:
            logger.info('Received message for a different group.')
            return JsonResponse({}, status=200)

        message = Msg.create_incoming(channel,
                                      URN.from_tel(from_addr),
                                      content,
                                      external_id=data['uuid'],
                                      attachments=attachments)

        response_body = {
            'message_id': message.pk,
        }

        request_body = request.body
        request_method = request.method
        request_path = request.get_full_path()

        event = HttpEvent(request_method, request_path, request_body, 201,
                          json.dumps(response_body))
        ChannelLog.log_message(message, 'Handled inbound message.', event)
        return JsonResponse(response_body, status=201)
예제 #26
0
    def post(self, request, *args, **kwargs):
        from temba.msgs.models import Msg

        request_body = request.body
        request_method = request.method
        request_path = request.get_full_path()

        def log_channel(channel, description, event, is_error=False):
            return ChannelLog.objects.create(
                channel_id=channel.pk,
                is_error=is_error,
                request=event.request_body,
                response=event.response_body,
                url=event.url,
                method=event.method,
                response_status=event.status_code,
                description=description,
            )

        action = kwargs["action"].lower()
        request_uuid = kwargs["uuid"]

        data = json.loads(force_text(request_body))
        is_ussd = self.is_ussd_message(data)
        channel_data = data.get("channel_data", {})
        channel_types = ("JNU", "JN")

        # look up the channel
        channel = Channel.objects.filter(uuid=request_uuid, is_active=True, channel_type__in=channel_types).first()

        if not channel:
            return HttpResponse("Channel not found for id: %s" % request_uuid, status=400)

        auth = request.META.get("HTTP_AUTHORIZATION", "").split(" ")
        secret = channel.config.get(Channel.CONFIG_SECRET)
        if secret is not None and (len(auth) != 2 or auth[0] != "Token" or auth[1] != secret):
            return JsonResponse(dict(error="Incorrect authentication token"), status=401)

        # Junebug is sending an event
        if action == "event":
            expected_keys = ["event_type", "message_id", "timestamp"]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (", ".join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body, status, response_body)
                log_channel(channel, "Failed to handle event.", event, is_error=True)
                return HttpResponse(response_body, status=status)

            message_id = data["message_id"]
            event_type = data["event_type"]

            # look up the message
            message = Msg.objects.filter(channel=channel, external_id=message_id).select_related("channel")
            if not message:
                status = 400
                response_body = "Message with external id of '%s' not found" % (message_id,)
                event = HttpEvent(request_method, request_path, request_body, status, response_body)
                log_channel(channel, "Failed to handle %s event_type." % (event_type), event)
                return HttpResponse(response_body, status=status)

            if event_type == "submitted":
                for message_obj in message:
                    message_obj.status_sent()
            if event_type == "delivery_succeeded":
                for message_obj in message:
                    message_obj.status_delivered()
            elif event_type in ["delivery_failed", "rejected"]:
                for message_obj in message:
                    message_obj.status_fail()

            response_body = {"status": self.ACK, "message_ids": [message_obj.pk for message_obj in message]}
            event = HttpEvent(request_method, request_path, request_body, 200, json.dumps(response_body))
            log_channel(channel, "Handled %s event_type." % (event_type), event)
            # Let Junebug know we're happy
            return JsonResponse(response_body)

        # Handle an inbound message
        elif action == "inbound":
            expected_keys = [
                "channel_data",
                "from",
                "channel_id",
                "timestamp",
                "content",
                "to",
                "reply_to",
                "message_id",
            ]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (", ".join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body, status, response_body)
                log_channel(channel, "Failed to handle message.", event, is_error=True)
                return HttpResponse(response_body, status=status)

            if is_ussd:
                status = {"close": USSDSession.INTERRUPTED, "new": USSDSession.TRIGGERED}.get(
                    channel_data.get("session_event"), USSDSession.IN_PROGRESS
                )

                message_date = datetime.strptime(data["timestamp"], "%Y-%m-%d %H:%M:%S.%f")
                gmt_date = pytz.timezone("GMT").localize(message_date)
                # Use a session id if provided, otherwise fall back to using the `from` address as the identifier
                session_id = channel_data.get("session_id") or data["from"]

                connection = USSDSession.handle_incoming(
                    channel=channel,
                    urn=data["from"],
                    content=data["content"],
                    status=status,
                    date=gmt_date,
                    external_id=session_id,
                    message_id=data["message_id"],
                    starcode=data["to"],
                )

                if connection:
                    status = 200
                    response_body = {"status": self.ACK, "session_id": connection.pk}
                    event = HttpEvent(request_method, request_path, request_body, status, json.dumps(response_body))
                    log_channel(
                        channel, "Handled USSD message of %s session_event" % (channel_data["session_event"],), event
                    )
                    return JsonResponse(response_body, status=status)
                else:
                    status = 400
                    response_body = {"status": self.NACK, "reason": "No suitable session found for this message."}
                    event = HttpEvent(request_method, request_path, request_body, status, json.dumps(response_body))
                    log_channel(
                        channel,
                        "Failed to handle USSD message of %s session_event" % (channel_data["session_event"],),
                        event,
                    )
                    return JsonResponse(response_body, status=status)
            else:
                content = data["content"]
                message = Msg.create_incoming(channel, URN.from_tel(data["from"]), content)
                status = 200
                response_body = {"status": self.ACK, "message_id": message.pk}
                Msg.objects.filter(pk=message.id).update(external_id=data["message_id"])
                event = HttpEvent(request_method, request_path, request_body, status, json.dumps(response_body))
                ChannelLog.log_message(message, "Handled inbound message.", event)
                return JsonResponse(response_body, status=status)
예제 #27
0
    def get(self, request, *args, **kwargs):
        from temba.flows.models import FlowSession
        from temba.ivr.models import IVRCall

        action = kwargs["action"].lower()

        request_body = force_text(request.body)
        request_path = request.get_full_path()
        request_method = request.method

        request_uuid = kwargs["uuid"]

        if action == "event":
            if not request_body:
                return HttpResponse("")

            body_json = json.loads(request_body)
            status = body_json.get("status", None)
            duration = body_json.get("duration", None)
            call_uuid = body_json.get("uuid", None)
            conversation_uuid = body_json.get("conversation_uuid", None)

            if call_uuid is None:
                return HttpResponse("Missing uuid parameter, ignoring")

            call = IVRCall.objects.filter(external_id=call_uuid).first()
            if not call:
                # try looking up by the conversation uuid (inbound calls start with that)
                call = IVRCall.objects.filter(external_id=conversation_uuid).first()
                if call:
                    call.external_id = call_uuid
                    call.save()
                else:
                    response = dict(message="Call not found for %s" % call_uuid)
                    return JsonResponse(response)

            channel = call.channel
            channel_type = channel.channel_type
            call.update_status(status, duration, channel_type)
            call.save()

            response = dict(
                description="Updated call status", call=dict(status=call.get_status_display(), duration=call.duration)
            )

            event = HttpEvent(request_method, request_path, request_body, 200, json.dumps(response))
            ChannelLog.log_ivr_interaction(call, "Updated call status", event)

            if call.status == IVRCall.COMPLETED:
                # if our call is completed, hangup
                runs = FlowRun.objects.filter(connection=call)
                for run in runs:
                    if not run.is_completed():
                        run.set_completed(exit_uuid=None)

            return JsonResponse(response)

        if action == "answer":
            if not request_body:
                return HttpResponse("")

            body_json = json.loads(request_body)
            from_number = body_json.get("from", None)
            channel_number = body_json.get("to", None)
            external_id = body_json.get("conversation_uuid", None)

            if not from_number or not channel_number or not external_id:
                return HttpResponse("Missing parameters, Ignoring")

            # look up the channel
            address_q = Q(address=channel_number) | Q(address=("+" + channel_number))
            channel = Channel.objects.filter(address_q).filter(is_active=True, channel_type="NX").first()

            # make sure we got one, and that it matches the key for our org
            org_uuid = None
            if channel:
                org_uuid = channel.org.config.get(NEXMO_UUID, None)

            if not channel or org_uuid != request_uuid:
                return HttpResponse("Channel not found for number: %s" % channel_number, status=404)

            urn = URN.from_tel(from_number)
            contact, urn_obj = Contact.get_or_create(channel.org, urn, channel)

            flow = Trigger.find_flow_for_inbound_call(contact)

            if flow:
                call = IVRCall.create_incoming(channel, contact, urn_obj, channel.created_by, external_id)
                session = FlowSession.create(contact, connection=call)

                FlowRun.create(flow, contact, session=session, connection=call)
                response = Flow.handle_call(call)
                channel_type = channel.channel_type
                call.update_status("answered", None, channel_type)

                event = HttpEvent(request_method, request_path, request_body, 200, str(response))
                ChannelLog.log_ivr_interaction(call, "Incoming request for call", event)
                return JsonResponse(json.loads(str(response)), safe=False)
            else:
                # we don't have an inbound trigger to deal with this call.
                response = channel.generate_ivr_response()

                # say nothing and hangup, this is a little rude, but if we reject the call, then
                # they'll get a non-working number error. We send 'busy' when our server is down
                # so we don't want to use that here either.
                response.say("")
                response.hangup()

                # if they have a missed call trigger, fire that off
                Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL, channel)

                # either way, we need to hangup now
                return JsonResponse(json.loads(str(response)), safe=False)
예제 #28
0
class USSDSession(ChannelSession):
    USSD_PULL = INCOMING = 'I'
    USSD_PUSH = OUTGOING = 'O'

    objects = USSDQuerySet.as_manager()

    class Meta:
        proxy = True

    @property
    def should_end(self):
        return self.status == self.ENDING

    def mark_ending(self):  # session to be ended
        if self.status != self.ENDING:
            self.status = self.ENDING
            self.save(update_fields=['status'])

    def close(self):  # session has successfully ended
        if self.status == self.ENDING:
            self.status = self.COMPLETED
        else:
            self.status = self.INTERRUPTED

        self.ended_on = timezone.now()
        self.save(update_fields=['status', 'ended_on'])

    def start_async(self, flow, date, message_id):
        from temba.msgs.models import Msg, USSD
        message = Msg.objects.create(channel=self.channel,
                                     contact=self.contact,
                                     contact_urn=self.contact_urn,
                                     sent_on=date,
                                     connection=self,
                                     msg_type=USSD,
                                     external_id=message_id,
                                     created_on=timezone.now(),
                                     modified_on=timezone.now(),
                                     org=self.channel.org,
                                     direction=self.INCOMING)
        flow.start([], [self.contact],
                   start_msg=message,
                   restart_participants=True,
                   connection=self)

    def handle_async(self, urn, content, date, message_id):
        from temba.msgs.models import Msg, USSD
        Msg.create_incoming(channel=self.channel,
                            org=self.org,
                            urn=urn,
                            text=content or '',
                            date=date,
                            connection=self,
                            msg_type=USSD,
                            external_id=message_id)

    def handle_sync(self):  # pragma: needs cover
        # TODO: implement for InfoBip and other sync APIs
        pass

    @classmethod
    def handle_incoming(cls,
                        channel,
                        urn,
                        date,
                        external_id,
                        contact=None,
                        message_id=None,
                        status=None,
                        content=None,
                        starcode=None,
                        org=None,
                        async=True):

        trigger = None
        contact_urn = None

        # handle contact with channel
        urn = URN.from_tel(urn)

        if not contact:
            contact, contact_urn = Contact.get_or_create(
                channel.org, urn, channel)
        elif urn:
            contact_urn = ContactURN.get_or_create(org,
                                                   contact,
                                                   urn,
                                                   channel=channel)

        contact.set_preferred_channel(channel)

        if contact_urn:
            contact_urn.update_affinity(channel)

        # setup session
        defaults = dict(channel=channel,
                        contact=contact,
                        contact_urn=contact_urn,
                        org=channel.org if channel else contact.org)

        if status == cls.TRIGGERED:
            trigger = Trigger.find_trigger_for_ussd_session(contact, starcode)
            if not trigger:
                return False
            defaults.update(
                dict(started_on=date, direction=cls.USSD_PULL, status=status))

        elif status == cls.INTERRUPTED:
            defaults.update(dict(ended_on=date, status=status))

        else:
            defaults.update(dict(status=cls.IN_PROGRESS))

        # check if there's an initiated PUSH connection
        connection = cls.objects.get_initiated_push(contact)

        created = False
        if not connection:
            try:
                connection = cls.objects.select_for_update().exclude(status__in=ChannelSession.DONE)\
                                                            .get(external_id=external_id)
                created = False
                for k, v in six.iteritems(defaults):
                    setattr(connection, k, v() if callable(v) else v)
                connection.save()
            except cls.DoesNotExist:
                defaults['external_id'] = external_id
                connection = cls.objects.create(**defaults)
                FlowSession.create(contact, connection=connection)
                created = True
        else:
            defaults.update(dict(external_id=external_id))
            for key, value in six.iteritems(defaults):
                setattr(connection, key, value)
            connection.save()
            created = None

        # start session
        if created and async and trigger:
            connection.start_async(trigger.flow, date, message_id)
예제 #29
0
class USSDSession(ChannelSession):
    USSD_PULL = INCOMING = 'I'
    USSD_PUSH = OUTGOING = 'O'

    objects = USSDQuerySet.as_manager()

    class Meta:
        proxy = True

    def start_session_async(self, flow, urn, content, date, message_id):
        from temba.msgs.models import Msg, USSD
        message = Msg.objects.create(channel=self.channel,
                                     contact=self.contact,
                                     contact_urn=self.contact_urn,
                                     sent_on=date,
                                     session=self,
                                     msg_type=USSD,
                                     external_id=message_id,
                                     created_on=timezone.now(),
                                     modified_on=timezone.now(),
                                     org=self.channel.org,
                                     direction=self.INCOMING)
        flow.start([], [self.contact],
                   start_msg=message,
                   restart_participants=True,
                   session=self)

    def handle_session_async(self, urn, content, date, message_id):
        from temba.msgs.models import Msg, USSD
        Msg.create_incoming(channel=self.channel,
                            urn=urn,
                            text=content or '',
                            date=date,
                            session=self,
                            msg_type=USSD,
                            external_id=message_id)

    def handle_ussd_session_sync(self):  # pragma: needs cover
        # TODO: implement for InfoBip and other sync APIs
        pass

    @classmethod
    def handle_incoming(cls,
                        channel,
                        urn,
                        date,
                        external_id,
                        message_id=None,
                        status=None,
                        flow=None,
                        content=None,
                        starcode=None,
                        org=None,
                        async=True):

        trigger = None

        # handle contact with channel
        urn = URN.from_tel(urn)
        contact = Contact.get_or_create(channel.org,
                                        channel.created_by,
                                        urns=[urn],
                                        channel=channel)
        contact_urn = contact.urn_objects[urn]

        contact.set_preferred_channel(channel)
        contact_urn.update_affinity(channel)

        # setup session
        defaults = dict(channel=channel,
                        contact=contact,
                        contact_urn=contact_urn,
                        org=channel.org)

        if status == cls.TRIGGERED:
            trigger = Trigger.find_trigger_for_ussd_session(contact, starcode)
            if not trigger:
                return False
            defaults.update(
                dict(started_on=date, direction=cls.USSD_PULL, status=status))

        elif status == cls.INTERRUPTED:
            defaults.update(dict(ended_on=date, status=status))

        else:
            defaults.update(dict(status=USSDSession.IN_PROGRESS))

        # check if there's an initiated PUSH session
        session = cls.objects.get_initiated_push_session(contact)

        if not session:
            session, created = cls.objects.update_or_create(
                external_id=external_id, defaults=defaults)
        else:
            defaults.update(dict(external_id=external_id))
            for key, value in six.iteritems(defaults):
                setattr(session, key, value)
            session.save()
            created = None

        # start session
        if created and async and trigger:
            session.start_session_async(trigger.flow, urn, content, date,
                                        message_id)
예제 #30
0
    def post(self, request, *args, **kwargs):
        from temba.msgs.models import Msg

        request_body = request.body
        request_method = request.method
        request_path = request.get_full_path()

        def log_channel(channel, description, event, is_error=False):
            return ChannelLog.objects.create(channel_id=channel.pk,
                                             is_error=is_error,
                                             request=event.request_body,
                                             response=event.response_body,
                                             url=event.url,
                                             method=event.method,
                                             response_status=event.status_code,
                                             description=description)

        action = kwargs['action'].lower()
        request_uuid = kwargs['uuid']

        data = json.loads(force_text(request_body))
        is_ussd = self.is_ussd_message(data)
        channel_data = data.get('channel_data', {})
        channel_types = ('JNU', 'JN')

        # look up the channel
        channel = Channel.objects.filter(
            uuid=request_uuid, is_active=True,
            channel_type__in=channel_types).first()

        if not channel:
            return HttpResponse("Channel not found for id: %s" % request_uuid,
                                status=400)

        auth = request.META.get('HTTP_AUTHORIZATION', '').split(' ')
        secret = channel.config.get(Channel.CONFIG_SECRET)
        if secret is not None and (len(auth) != 2 or auth[0] != 'Token'
                                   or auth[1] != secret):
            return JsonResponse(dict(error="Incorrect authentication token"),
                                status=401)

        # Junebug is sending an event
        if action == 'event':
            expected_keys = ["event_type", "message_id", "timestamp"]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (
                    ', '.join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            'Failed to handle event.',
                            event,
                            is_error=True)
                return HttpResponse(response_body, status=status)

            message_id = data['message_id']
            event_type = data["event_type"]

            # look up the message
            message = Msg.objects.filter(
                channel=channel,
                external_id=message_id).select_related('channel')
            if not message:
                status = 400
                response_body = "Message with external id of '%s' not found" % (
                    message_id, )
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            'Failed to handle %s event_type.' % (event_type),
                            event)
                return HttpResponse(response_body, status=status)

            if event_type == 'submitted':
                for message_obj in message:
                    message_obj.status_sent()
            if event_type == 'delivery_succeeded':
                for message_obj in message:
                    message_obj.status_delivered()
            elif event_type in ['delivery_failed', 'rejected']:
                for message_obj in message:
                    message_obj.status_fail()

            response_body = {
                'status': self.ACK,
                'message_ids': [message_obj.pk for message_obj in message]
            }
            event = HttpEvent(request_method, request_path, request_body, 200,
                              json.dumps(response_body))
            log_channel(channel, 'Handled %s event_type.' % (event_type),
                        event)
            # Let Junebug know we're happy
            return JsonResponse(response_body)

        # Handle an inbound message
        elif action == 'inbound':
            expected_keys = [
                'channel_data',
                'from',
                'channel_id',
                'timestamp',
                'content',
                'to',
                'reply_to',
                'message_id',
            ]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (
                    ', '.join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            'Failed to handle message.',
                            event,
                            is_error=True)
                return HttpResponse(response_body, status=status)

            if is_ussd:
                status = {
                    'close': USSDSession.INTERRUPTED,
                    'new': USSDSession.TRIGGERED,
                }.get(channel_data.get('session_event'),
                      USSDSession.IN_PROGRESS)

                message_date = datetime.strptime(data['timestamp'],
                                                 "%Y-%m-%d %H:%M:%S.%f")
                gmt_date = pytz.timezone('GMT').localize(message_date)
                # Use a session id if provided, otherwise fall back to using the `from` address as the identifier
                session_id = channel_data.get('session_id') or data['from']

                connection = USSDSession.handle_incoming(
                    channel=channel,
                    urn=data['from'],
                    content=data['content'],
                    status=status,
                    date=gmt_date,
                    external_id=session_id,
                    message_id=data['message_id'],
                    starcode=data['to'])

                if connection:
                    status = 200
                    response_body = {
                        'status': self.ACK,
                        'session_id': connection.pk,
                    }
                    event = HttpEvent(request_method, request_path,
                                      request_body, status,
                                      json.dumps(response_body))
                    log_channel(
                        channel, 'Handled USSD message of %s session_event' %
                        (channel_data['session_event'], ), event)
                    return JsonResponse(response_body, status=status)
                else:
                    status = 400
                    response_body = {
                        'status': self.NACK,
                        'reason': 'No suitable session found for this message.'
                    }
                    event = HttpEvent(request_method, request_path,
                                      request_body, status,
                                      json.dumps(response_body))
                    log_channel(
                        channel,
                        'Failed to handle USSD message of %s session_event' %
                        (channel_data['session_event'], ), event)
                    return JsonResponse(response_body, status=status)
            else:
                content = data['content']
                message = Msg.create_incoming(channel,
                                              URN.from_tel(data['from']),
                                              content)
                status = 200
                response_body = {
                    'status': self.ACK,
                    'message_id': message.pk,
                }
                Msg.objects.filter(pk=message.id).update(
                    external_id=data['message_id'])
                event = HttpEvent(request_method, request_path, request_body,
                                  status, json.dumps(response_body))
                ChannelLog.log_message(message, 'Handled inbound message.',
                                       event)
                return JsonResponse(response_body, status=status)
예제 #31
0
    def post(self, request, *args, **kwargs):
        from temba.msgs.models import Msg

        request_body = request.body
        request_method = request.method
        request_path = request.get_full_path()

        def log_channel(channel, description, event, is_error=False):
            return ChannelLog.objects.create(
                channel_id=channel.pk,
                is_error=is_error,
                request=event.request_body,
                response=event.response_body,
                url=event.url,
                method=event.method,
                response_status=event.status_code,
                description=description,
            )

        action = kwargs["action"].lower()
        request_uuid = kwargs["uuid"]

        data = json.loads(force_text(request_body))
        is_ussd = self.is_ussd_message(data)
        channel_data = data.get("channel_data", {})
        channel_types = ("JNU", "JN")

        # look up the channel
        channel = Channel.objects.filter(
            uuid=request_uuid, is_active=True,
            channel_type__in=channel_types).first()

        if not channel:
            return HttpResponse("Channel not found for id: %s" % request_uuid,
                                status=400)

        auth = request.META.get("HTTP_AUTHORIZATION", "").split(" ")
        secret = channel.config.get(Channel.CONFIG_SECRET)
        if secret is not None and (len(auth) != 2 or auth[0] != "Token"
                                   or auth[1] != secret):
            return JsonResponse(dict(error="Incorrect authentication token"),
                                status=401)

        # Junebug is sending an event
        if action == "event":
            expected_keys = ["event_type", "message_id", "timestamp"]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (
                    ", ".join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            "Failed to handle event.",
                            event,
                            is_error=True)
                return HttpResponse(response_body, status=status)

            message_id = data["message_id"]
            event_type = data["event_type"]

            # look up the message
            message = Msg.objects.filter(
                channel=channel,
                external_id=message_id).select_related("channel")
            if not message:
                status = 400
                response_body = "Message with external id of '%s' not found" % (
                    message_id, )
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            "Failed to handle %s event_type." % (event_type),
                            event)
                return HttpResponse(response_body, status=status)

            if event_type == "submitted":
                for message_obj in message:
                    message_obj.status_sent()
            if event_type == "delivery_succeeded":
                for message_obj in message:
                    message_obj.status_delivered()
            elif event_type in ["delivery_failed", "rejected"]:
                for message_obj in message:
                    message_obj.status_fail()

            response_body = {
                "status": self.ACK,
                "message_ids": [message_obj.pk for message_obj in message]
            }
            event = HttpEvent(request_method, request_path, request_body, 200,
                              json.dumps(response_body))
            log_channel(channel, "Handled %s event_type." % (event_type),
                        event)
            # Let Junebug know we're happy
            return JsonResponse(response_body)

        # Handle an inbound message
        elif action == "inbound":
            expected_keys = [
                "channel_data",
                "from",
                "channel_id",
                "timestamp",
                "content",
                "to",
                "reply_to",
                "message_id",
            ]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (
                    ", ".join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            "Failed to handle message.",
                            event,
                            is_error=True)
                return HttpResponse(response_body, status=status)

            if is_ussd:
                status = {
                    "close": USSDSession.INTERRUPTED,
                    "new": USSDSession.TRIGGERED
                }.get(channel_data.get("session_event"),
                      USSDSession.IN_PROGRESS)

                message_date = datetime.strptime(data["timestamp"],
                                                 "%Y-%m-%d %H:%M:%S.%f")
                gmt_date = pytz.timezone("GMT").localize(message_date)
                # Use a session id if provided, otherwise fall back to using the `from` address as the identifier
                session_id = channel_data.get("session_id") or data["from"]

                connection = USSDSession.handle_incoming(
                    channel=channel,
                    urn=data["from"],
                    content=data["content"],
                    status=status,
                    date=gmt_date,
                    external_id=session_id,
                    message_id=data["message_id"],
                    starcode=data["to"],
                )

                if connection:
                    status = 200
                    response_body = {
                        "status": self.ACK,
                        "session_id": connection.pk
                    }
                    event = HttpEvent(request_method, request_path,
                                      request_body, status,
                                      json.dumps(response_body))
                    log_channel(
                        channel, "Handled USSD message of %s session_event" %
                        (channel_data["session_event"], ), event)
                    return JsonResponse(response_body, status=status)
                else:
                    status = 400
                    response_body = {
                        "status": self.NACK,
                        "reason": "No suitable session found for this message."
                    }
                    event = HttpEvent(request_method, request_path,
                                      request_body, status,
                                      json.dumps(response_body))
                    log_channel(
                        channel,
                        "Failed to handle USSD message of %s session_event" %
                        (channel_data["session_event"], ),
                        event,
                    )
                    return JsonResponse(response_body, status=status)
            else:
                content = data["content"]
                message = Msg.create_incoming(channel,
                                              URN.from_tel(data["from"]),
                                              content)
                status = 200
                response_body = {"status": self.ACK, "message_id": message.pk}
                Msg.objects.filter(pk=message.id).update(
                    external_id=data["message_id"])
                event = HttpEvent(request_method, request_path, request_body,
                                  status, json.dumps(response_body))
                ChannelLog.log_message(message, "Handled inbound message.",
                                       event)
                return JsonResponse(response_body, status=status)
예제 #32
0
    def post(self, request, *args, **kwargs):
        from twilio.request_validator import RequestValidator
        from temba.flows.models import FlowSession

        signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")
        url = "https://" + request.get_host() + "%s" % request.get_full_path()

        channel_uuid = kwargs.get("uuid")
        call_sid = self.get_param("CallSid")
        direction = self.get_param("Direction")
        status = self.get_param("CallStatus")
        to_number = self.get_param("To")
        to_country = self.get_param("ToCountry")
        from_number = self.get_param("From")

        # Twilio sometimes sends un-normalized numbers
        if to_number and not to_number.startswith("+") and to_country:  # pragma: no cover
            to_number, valid = URN.normalize_number(to_number, to_country)

        # see if it's a twilio call being initiated
        if to_number and call_sid and direction == "inbound" and status == "ringing":

            # find a channel that knows how to answer twilio calls
            channel = self.get_ringing_channel(uuid=channel_uuid)
            if not channel:
                response = VoiceResponse()
                response.say("Sorry, there is no channel configured to take this call. Goodbye.")
                response.hangup()
                return HttpResponse(str(response))

            org = channel.org

            if self.get_channel_type() == "T" and not org.is_connected_to_twilio():
                return HttpResponse("No Twilio account is connected", status=400)

            client = self.get_client(channel=channel)
            validator = RequestValidator(client.auth[1])
            signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")

            url = "https://%s%s" % (request.get_host(), request.get_full_path())

            if validator.validate(url, request.POST, signature):
                from temba.ivr.models import IVRCall

                # find a contact for the one initiating us
                urn = URN.from_tel(from_number)
                contact, urn_obj = Contact.get_or_create(channel.org, urn, channel)

                flow = Trigger.find_flow_for_inbound_call(contact)

                if flow:
                    call = IVRCall.create_incoming(channel, contact, urn_obj, channel.created_by, call_sid)
                    session = FlowSession.create(contact, connection=call)

                    call.update_status(
                        request.POST.get("CallStatus", None), request.POST.get("CallDuration", None), "T"
                    )
                    call.save()

                    FlowRun.create(flow, contact, session=session, connection=call)
                    response = Flow.handle_call(call)
                    return HttpResponse(str(response))

                else:

                    # we don't have an inbound trigger to deal with this call.
                    response = channel.generate_ivr_response()

                    # say nothing and hangup, this is a little rude, but if we reject the call, then
                    # they'll get a non-working number error. We send 'busy' when our server is down
                    # so we don't want to use that here either.
                    response.say("")
                    response.hangup()

                    # if they have a missed call trigger, fire that off
                    Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL, channel)

                    # either way, we need to hangup now
                    return HttpResponse(str(response))

        # check for call progress events, these include post-call hangup notifications
        if request.POST.get("CallbackSource", None) == "call-progress-events":
            if call_sid:
                from temba.ivr.models import IVRCall

                call = IVRCall.objects.filter(external_id=call_sid).first()
                if call:
                    call.update_status(
                        request.POST.get("CallStatus", None), request.POST.get("CallDuration", None), "TW"
                    )
                    call.save()
                    return HttpResponse("Call status updated")
            return HttpResponse("No call found")

        return HttpResponse("Not Handled, unknown action", status=400)  # pragma: no cover
예제 #33
0
    def handle_incoming(
        cls,
        channel,
        urn,
        date,
        external_id,
        contact=None,
        message_id=None,
        status=None,
        content=None,
        starcode=None,
        org=None,
        do_async=True,
    ):

        trigger = None
        contact_urn = None

        # handle contact with channel
        urn = URN.from_tel(urn)

        if not contact:
            contact, contact_urn = Contact.get_or_create(
                channel.org, urn, channel)
        elif urn:
            contact_urn = ContactURN.get_or_create(org,
                                                   contact,
                                                   urn,
                                                   channel=channel)

        contact.set_preferred_channel(channel)

        if contact_urn:
            contact_urn.update_affinity(channel)

        # setup session
        defaults = dict(channel=channel,
                        contact=contact,
                        contact_urn=contact_urn,
                        org=channel.org if channel else contact.org)

        if status == cls.TRIGGERED:
            trigger = Trigger.find_trigger_for_ussd_session(contact, starcode)
            if not trigger:
                return False
            defaults.update(
                dict(started_on=date, direction=cls.USSD_PULL, status=status))

        elif status == cls.INTERRUPTED:
            defaults.update(dict(ended_on=date, status=status))

        else:
            defaults.update(dict(status=cls.IN_PROGRESS))

        # check if there's an initiated PUSH connection
        connection = cls.objects.get_initiated_push(contact)

        created = False
        if not connection:
            try:
                connection = (cls.objects.select_for_update().exclude(
                    status__in=ChannelSession.DONE).get(
                        external_id=external_id))
                created = False
                for k, v in defaults.items():
                    setattr(connection, k, v() if callable(v) else v)
                connection.save()
            except cls.DoesNotExist:
                defaults["external_id"] = external_id
                connection = cls.objects.create(**defaults)
                FlowSession.create(contact, connection=connection)
                created = True
        else:
            defaults.update(dict(external_id=external_id))
            for key, value in defaults.items():
                setattr(connection, key, value)
            connection.save()
            created = None

        # start session
        if created and do_async and trigger:
            connection.start_async(trigger.flow, date, message_id)

        # resume session, deal with incoming content and all the other states
        else:
            connection.handle_async(urn, content, date, message_id)

        return connection
예제 #34
0
    def get_or_create_contact(self, urn):
        if ":" not in urn:
            urn = URN.from_tel(urn)  # assume phone number

        contact, urn_obj = Contact.get_or_create(self.org, urn, user=self.user)
        return contact
예제 #35
0
    def handle_incoming(
        cls,
        channel,
        urn,
        date,
        external_id,
        contact=None,
        message_id=None,
        status=None,
        content=None,
        starcode=None,
        org=None,
        do_async=True,
    ):

        trigger = None
        contact_urn = None

        # handle contact with channel
        urn = URN.from_tel(urn)

        if not contact:
            contact, contact_urn = Contact.get_or_create(channel.org, urn, channel)
        elif urn:
            contact_urn = ContactURN.get_or_create(org, contact, urn, channel=channel)

        contact.set_preferred_channel(channel)

        if contact_urn:
            contact_urn.update_affinity(channel)

        # setup session
        defaults = dict(
            channel=channel, contact=contact, contact_urn=contact_urn, org=channel.org if channel else contact.org
        )

        if status == cls.TRIGGERED:
            trigger = Trigger.find_trigger_for_ussd_session(contact, starcode)
            if not trigger:
                return False
            defaults.update(dict(started_on=date, direction=cls.USSD_PULL, status=status))

        elif status == cls.INTERRUPTED:
            defaults.update(dict(ended_on=date, status=status))

        else:
            defaults.update(dict(status=cls.IN_PROGRESS))

        # check if there's an initiated PUSH connection
        connection = cls.objects.get_initiated_push(contact)

        created = False
        if not connection:
            try:
                connection = (
                    cls.objects.select_for_update()
                    .exclude(status__in=ChannelConnection.DONE)
                    .get(external_id=external_id)
                )
                created = False
                for k, v in defaults.items():
                    setattr(connection, k, v() if callable(v) else v)
                connection.save()
            except cls.DoesNotExist:
                defaults["external_id"] = external_id
                connection = cls.objects.create(**defaults)
                FlowSession.create(contact, connection=connection)
                created = True
        else:
            defaults.update(dict(external_id=external_id))
            for key, value in defaults.items():
                setattr(connection, key, value)
            connection.save()
            created = None

        # start session
        if created and do_async and trigger:
            connection.start_async(trigger.flow, date, message_id)

        # resume session, deal with incoming content and all the other states
        else:
            connection.handle_async(urn, content, date, message_id)

        return connection
예제 #36
0
    def create_contacts(self, orgs, locations, num_total):
        batch_size = 5000
        num_test_contacts = len(orgs) * len(USERS)
        group_membership_model = ContactGroup.contacts.through
        group_counts = defaultdict(int)

        self._log("Creating %d test contacts...\n" % num_test_contacts)

        for org in orgs:
            for user in org.cache['users']:
                Contact.get_test_contact(user)

        self._log("Creating %d regular contacts...\n" % (num_total - num_test_contacts))

        base_contact_id = self.get_current_id(Contact) + 1

        # Disable table triggers to speed up insertion and in the case of contact group m2m, avoid having an unsquashed
        # count row for every contact
        with DisableTriggersOn(Contact, ContactURN, Value, group_membership_model):
            names = [('%s %s' % (c1, c2)).strip() for c2 in CONTACT_NAMES[1] for c1 in CONTACT_NAMES[0]]
            names = [n if n else None for n in names]

            batch = 1
            for index_batch in chunk_list(range(num_total - num_test_contacts), batch_size):
                contacts = []
                urns = []
                values = []
                memberships = []

                def add_to_group(g):
                    group_counts[g] += 1
                    memberships.append(group_membership_model(contact_id=c['id'], contactgroup=g))

                for c_index in index_batch:  # pragma: no cover

                    org = orgs[c_index] if c_index < len(orgs) else self.random_org(orgs)  # at least 1 contact per org
                    name = self.random_choice(names)
                    location = self.random_choice(locations) if self.probability(CONTACT_HAS_FIELD_PROB) else None
                    created_on = self.timeline_date(float(num_test_contacts + c_index) / num_total)

                    c = {
                        'id': base_contact_id + c_index,  # database id this contact will have when created
                        'org': org,
                        'user': org.cache['users'][0],
                        'name': name,
                        'tel': '+2507%08d' % c_index if self.probability(CONTACT_HAS_TEL_PROB) else None,
                        'twitter': '%s%d' % (name.replace(' ', '_').lower() if name else 'tweep', c_index) if self.probability(CONTACT_HAS_TWITTER_PROB) else None,
                        'gender': self.random_choice(('M', 'F')) if self.probability(CONTACT_HAS_FIELD_PROB) else None,
                        'age': self.random.randint(16, 80) if self.probability(CONTACT_HAS_FIELD_PROB) else None,
                        'joined': self.random_date() if self.probability(CONTACT_HAS_FIELD_PROB) else None,
                        'ward': location[0] if location else None,
                        'district': location[1] if location else None,
                        'state': location[2] if location else None,
                        'language': self.random_choice(CONTACT_LANGS),
                        'is_stopped': self.probability(CONTACT_IS_STOPPED_PROB),
                        'is_blocked': self.probability(CONTACT_IS_BLOCKED_PROB),
                        'is_active': self.probability(1 - CONTACT_IS_DELETED_PROB),
                        'created_on': created_on,
                        'modified_on': self.random_date(created_on, self.db_ends_on),
                    }

                    if c['is_active']:
                        if not c['is_blocked'] and not c['is_stopped']:
                            add_to_group(org.cache['system_groups'][ContactGroup.TYPE_ALL])
                        if c['is_blocked']:
                            add_to_group(org.cache['system_groups'][ContactGroup.TYPE_BLOCKED])
                        if c['is_stopped']:
                            add_to_group(org.cache['system_groups'][ContactGroup.TYPE_STOPPED])

                    contacts.append(Contact(org=org, name=c['name'], language=c['language'],
                                            is_stopped=c['is_stopped'], is_blocked=c['is_blocked'],
                                            is_active=c['is_active'],
                                            created_by=user, created_on=c['created_on'],
                                            modified_by=user, modified_on=c['modified_on']))

                    if c['tel']:
                        urns.append(ContactURN(org=org, contact_id=c['id'], priority=50, scheme=TEL_SCHEME,
                                               path=c['tel'], urn=URN.from_tel(c['tel'])))
                    if c['twitter']:
                        urns.append(ContactURN(org=org, contact_id=c['id'], priority=50, scheme=TWITTER_SCHEME,
                                               path=c['twitter'], urn=URN.from_twitter(c['twitter'])))
                    if c['gender']:
                        values.append(Value(org=org, contact_id=c['id'], contact_field=org.cache['fields']['gender'],
                                            string_value=c['gender']))
                    if c['age']:
                        values.append(Value(org=org, contact_id=c['id'], contact_field=org.cache['fields']['age'],
                                            string_value=str(c['age']), decimal_value=c['age']))
                    if c['joined']:
                        values.append(Value(org=org, contact_id=c['id'], contact_field=org.cache['fields']['joined'],
                                            string_value=datetime_to_str(c['joined']), datetime_value=c['joined']))
                    if location:
                        values.append(Value(org=org, contact_id=c['id'], contact_field=org.cache['fields']['ward'],
                                            string_value=c['ward'].name, location_value=c['ward']))
                        values.append(Value(org=org, contact_id=c['id'], contact_field=org.cache['fields']['district'],
                                            string_value=c['district'].name, location_value=c['district']))
                        values.append(Value(org=org, contact_id=c['id'], contact_field=org.cache['fields']['state'],
                                            string_value=c['state'].name, location_value=c['state']))

                    # let each group decide if it is taking this contact
                    for g in org.cache['groups']:
                        if g.member(c) if callable(g.member) else self.probability(g.member):
                            add_to_group(g)

                Contact.objects.bulk_create(contacts)
                ContactURN.objects.bulk_create(urns)
                Value.objects.bulk_create(values)
                group_membership_model.objects.bulk_create(memberships)

                self._log(" > Created batch %d of %d\n" % (batch, max(num_total // batch_size, 1)))
                batch += 1

        # create group count records manually
        counts = []
        for group, count in group_counts.items():
            counts.append(ContactGroupCount(group=group, count=count, is_squashed=True))
        ContactGroupCount.objects.bulk_create(counts)

        # for sanity check that our presumed last contact id matches the last actual contact id
        assert c['id'] == Contact.objects.order_by('-id').first().id
예제 #37
0
    def get_or_create_contact(self, urn):
        if ':' not in urn:
            urn = URN.from_tel(urn)  # assume phone number

        return Contact.get_or_create(self.org, self.user, name=None, urns=[urn])
예제 #38
0
    def post(self, request, *args, **kwargs):
        from twilio.request_validator import RequestValidator
        from temba.flows.models import FlowSession

        signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")
        url = "https://" + request.get_host() + "%s" % request.get_full_path()

        channel_uuid = kwargs.get("uuid")
        call_sid = self.get_param("CallSid")
        direction = self.get_param("Direction")
        status = self.get_param("CallStatus")
        to_number = self.get_param("To")
        to_country = self.get_param("ToCountry")
        from_number = self.get_param("From")

        # Twilio sometimes sends un-normalized numbers
        if to_number and not to_number.startswith(
                "+") and to_country:  # pragma: no cover
            to_number, valid = URN.normalize_number(to_number, to_country)

        # see if it's a twilio call being initiated
        if to_number and call_sid and direction == "inbound" and status == "ringing":

            # find a channel that knows how to answer twilio calls
            channel = self.get_ringing_channel(uuid=channel_uuid)
            if not channel:
                response = VoiceResponse()
                response.say(
                    "Sorry, there is no channel configured to take this call. Goodbye."
                )
                response.hangup()
                return HttpResponse(str(response))

            org = channel.org

            if self.get_channel_type(
            ) == "T" and not org.is_connected_to_twilio():
                return HttpResponse("No Twilio account is connected",
                                    status=400)

            client = self.get_client(channel=channel)
            validator = RequestValidator(client.auth[1])
            signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "")

            url = "https://%s%s" % (request.get_host(),
                                    request.get_full_path())

            if validator.validate(url, request.POST, signature):
                from temba.ivr.models import IVRCall

                # find a contact for the one initiating us
                urn = URN.from_tel(from_number)
                contact, urn_obj = Contact.get_or_create(
                    channel.org, urn, channel)

                flow = Trigger.find_flow_for_inbound_call(contact)

                if flow:
                    call = IVRCall.create_incoming(channel, contact, urn_obj,
                                                   channel.created_by,
                                                   call_sid)
                    session = FlowSession.create(contact, connection=call)

                    call.update_status(request.POST.get("CallStatus", None),
                                       request.POST.get("CallDuration", None),
                                       "T")
                    call.save()

                    FlowRun.create(flow,
                                   contact,
                                   session=session,
                                   connection=call)
                    response = Flow.handle_call(call)
                    return HttpResponse(str(response))

                else:

                    # we don't have an inbound trigger to deal with this call.
                    response = channel.generate_ivr_response()

                    # say nothing and hangup, this is a little rude, but if we reject the call, then
                    # they'll get a non-working number error. We send 'busy' when our server is down
                    # so we don't want to use that here either.
                    response.say("")
                    response.hangup()

                    # if they have a missed call trigger, fire that off
                    Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL,
                                           channel)

                    # either way, we need to hangup now
                    return HttpResponse(str(response))

        # check for call progress events, these include post-call hangup notifications
        if request.POST.get("CallbackSource", None) == "call-progress-events":
            if call_sid:
                from temba.ivr.models import IVRCall

                call = IVRCall.objects.filter(external_id=call_sid).first()
                if call:
                    call.update_status(request.POST.get("CallStatus", None),
                                       request.POST.get("CallDuration", None),
                                       "TW")
                    call.save()
                    return HttpResponse("Call status updated")
            return HttpResponse("No call found")

        return HttpResponse("Not Handled, unknown action",
                            status=400)  # pragma: no cover
예제 #39
0
    def get(self, request, *args, **kwargs):
        from temba.flows.models import FlowSession
        from temba.ivr.models import IVRCall

        action = kwargs["action"].lower()

        request_body = force_text(request.body)
        request_path = request.get_full_path()
        request_method = request.method

        request_uuid = kwargs["uuid"]

        if action == "event":
            if not request_body:
                return HttpResponse("")

            body_json = json.loads(request_body)
            status = body_json.get("status", None)
            duration = body_json.get("duration", None)
            call_uuid = body_json.get("uuid", None)
            conversation_uuid = body_json.get("conversation_uuid", None)

            if call_uuid is None:
                return HttpResponse("Missing uuid parameter, ignoring")

            call = IVRCall.objects.filter(external_id=call_uuid).first()
            if not call:
                # try looking up by the conversation uuid (inbound calls start with that)
                call = IVRCall.objects.filter(
                    external_id=conversation_uuid).first()
                if call:
                    call.external_id = call_uuid
                    call.save()
                else:
                    response = dict(message="Call not found for %s" %
                                    call_uuid)
                    return JsonResponse(response)

            channel = call.channel
            channel_type = channel.channel_type
            call.update_status(status, duration, channel_type)
            call.save()

            response = dict(description="Updated call status",
                            call=dict(status=call.get_status_display(),
                                      duration=call.duration))

            event = HttpEvent(request_method, request_path, request_body, 200,
                              json.dumps(response))
            ChannelLog.log_ivr_interaction(call, "Updated call status", event)

            if call.status == IVRCall.COMPLETED:
                # if our call is completed, hangup
                runs = FlowRun.objects.filter(connection=call)
                for run in runs:
                    if not run.is_completed():
                        run.set_completed(exit_uuid=None)

            return JsonResponse(response)

        if action == "answer":
            if not request_body:
                return HttpResponse("")

            body_json = json.loads(request_body)
            from_number = body_json.get("from", None)
            channel_number = body_json.get("to", None)
            external_id = body_json.get("conversation_uuid", None)

            if not from_number or not channel_number or not external_id:
                return HttpResponse("Missing parameters, Ignoring")

            # look up the channel
            address_q = Q(address=channel_number) | Q(address=("+" +
                                                               channel_number))
            channel = Channel.objects.filter(address_q).filter(
                is_active=True, channel_type="NX").first()

            # make sure we got one, and that it matches the key for our org
            org_uuid = None
            if channel:
                org_uuid = channel.org.config.get(NEXMO_UUID, None)

            if not channel or org_uuid != request_uuid:
                return HttpResponse("Channel not found for number: %s" %
                                    channel_number,
                                    status=404)

            urn = URN.from_tel(from_number)
            contact, urn_obj = Contact.get_or_create(channel.org, urn, channel)

            flow = Trigger.find_flow_for_inbound_call(contact)

            if flow:
                call = IVRCall.create_incoming(channel, contact, urn_obj,
                                               channel.created_by, external_id)
                session = FlowSession.create(contact, connection=call)

                FlowRun.create(flow, contact, session=session, connection=call)
                response = Flow.handle_call(call)
                channel_type = channel.channel_type
                call.update_status("answered", None, channel_type)

                event = HttpEvent(request_method, request_path, request_body,
                                  200, str(response))
                ChannelLog.log_ivr_interaction(call,
                                               "Incoming request for call",
                                               event)
                return JsonResponse(json.loads(str(response)), safe=False)
            else:
                # we don't have an inbound trigger to deal with this call.
                response = channel.generate_ivr_response()

                # say nothing and hangup, this is a little rude, but if we reject the call, then
                # they'll get a non-working number error. We send 'busy' when our server is down
                # so we don't want to use that here either.
                response.say("")
                response.hangup()

                # if they have a missed call trigger, fire that off
                Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL,
                                       channel)

                # either way, we need to hangup now
                return JsonResponse(json.loads(str(response)), safe=False)