Пример #1
0
    def validate(self, data):
        urns = data.get('urn', [])
        phones = data.get('phone', [])
        contacts = data.get('contact', [])
        channel = data.get('channel')

        if (not urns and not phones and not contacts) or (urns and phones):  # pragma: needs cover
            raise serializers.ValidationError("Must provide either urns or phone or contact and not both")

        if not channel:
            channel = Channel.objects.filter(is_active=True, org=self.org).order_by('-last_seen').first()
            if not channel:  # pragma: no cover
                raise serializers.ValidationError("There are no channels for this organization.")
            data['channel'] = channel

        if phones:
            if self.org.is_anon:  # pragma: needs cover
                raise serializers.ValidationError("Cannot create messages for anonymous organizations")

            # check our numbers for validity
            country = channel.country
            for urn in phones:
                try:
                    tel, phone, display = URN.to_parts(urn)
                    normalized = phonenumbers.parse(phone, country.code)
                    if not phonenumbers.is_possible_number(normalized):  # pragma: needs cover
                        raise serializers.ValidationError("Invalid phone number: '%s'" % phone)
                except Exception:
                    raise serializers.ValidationError("Invalid phone number: '%s'" % phone)

        return data
Пример #2
0
def serialize_contact(contact):
    from temba.contacts.models import URN

    field_values = {}
    for field in contact.org.cached_contact_fields.values():
        field_values[field.key] = contact.get_field_json(field)

    # augment URN values with preferred channel UUID as a parameter
    urn_values = []
    for u in contact.urns.order_by("-priority", "id"):
        # for each URN we include the preferred channel as a query param if there is one
        if u.channel and u.channel.is_active:
            scheme, path, query, display = URN.to_parts(u.urn)
            urn_str = URN.from_parts(scheme, path, query=urlencode({"channel": str(u.channel.uuid)}), display=display)
        else:
            urn_str = u.urn

        urn_values.append(urn_str)

    return {
        "uuid": contact.uuid,
        "id": contact.id,
        "name": contact.name,
        "language": contact.language,
        "urns": urn_values,
        "groups": [serialize_ref(group) for group in contact.user_groups.filter(is_active=True)],
        "fields": field_values,
    }
Пример #3
0
    def validate(self, data):
        urns = data.get("urn", [])
        phones = data.get("phone", [])
        contacts = data.get("contact", [])
        channel = data.get("channel")

        if (not urns and not phones and not contacts) or (urns and phones):  # pragma: needs cover
            raise serializers.ValidationError("Must provide either urns or phone or contact and not both")

        if not channel:
            channel = Channel.objects.filter(is_active=True, org=self.org).order_by("-last_seen").first()
            if not channel:  # pragma: no cover
                raise serializers.ValidationError("There are no channels for this organization.")
            data["channel"] = channel

        if phones:
            if self.org.is_anon:  # pragma: needs cover
                raise serializers.ValidationError("Cannot create messages for anonymous organizations")

            # check our numbers for validity
            country = channel.country
            for urn in phones:
                try:
                    tel, phone, query, display = URN.to_parts(urn)
                    normalized = phonenumbers.parse(phone, country.code)
                    if not phonenumbers.is_possible_number(normalized):  # pragma: needs cover
                        raise serializers.ValidationError("Invalid phone number: '%s'" % phone)
                except Exception:
                    raise serializers.ValidationError("Invalid phone number: '%s'" % phone)

        return data
Пример #4
0
    def send(self, channel, msg, text):
        twitter = TembaTwython.from_channel(channel)
        start = time.time()

        try:
            urn = getattr(msg, "urn", URN.from_twitter(msg.urn_path))
            (scheme, path, query, display) = URN.to_parts(urn)

            # this is a legacy URN (no display), the path is our screen name
            if scheme == TWITTER_SCHEME:
                dm = twitter.send_direct_message(screen_name=path, text=text)
                external_id = dm["id"]

            # this is a new twitterid URN, our path is our user id
            else:
                metadata = msg.metadata if hasattr(msg, "metadata") else {}
                quick_replies = metadata.get("quick_replies", [])
                formatted_replies = [dict(label=item[: self.quick_reply_text_size]) for item in quick_replies]

                if quick_replies:
                    params = {
                        "event": {
                            "type": "message_create",
                            "message_create": {
                                "target": {"recipient_id": path},
                                "message_data": {
                                    "text": text,
                                    "quick_reply": {"type": "options", "options": formatted_replies},
                                },
                            },
                        }
                    }
                    dm = twitter.post("direct_messages/events/new", params=params)
                    external_id = dm["event"]["id"]
                else:
                    dm = twitter.send_direct_message(user_id=path, text=text)
                    external_id = dm["id"]

        except Exception as e:
            error_code = getattr(e, "error_code", 400)
            fatal = False

            if error_code == 404:  # handle doesn't exist
                fatal = True
            elif error_code == 403:
                for err in self.FATAL_403S:
                    if str(e).find(err) >= 0:
                        fatal = True
                        break

            # if message can never be sent, stop them contact
            if fatal:
                contact = Contact.objects.get(id=msg.contact)
                contact.stop(contact.modified_by)

            raise SendException(str(e), events=twitter.events, fatal=fatal, start=start)

        Channel.success(channel, msg, WIRED, start, events=twitter.events, external_id=external_id)
Пример #5
0
    def handle(self, *args, **options):
        colorama_init()
        org = get_org(options["org"])
        scheme, path, *rest = URN.to_parts(options["urn"])

        db = settings.DATABASES["default"]
        db_url = f"postgres://{db['USER']}:{db['PASSWORD']}@{db['HOST']}:{db['PORT']}/{db['NAME']}?sslmode=disable"
        redis_url = settings.CACHES["default"]["LOCATION"]

        self.prompt = f"📱 {Fore.CYAN}{path}{Fore.RESET}> "

        try:
            print(
                f"✅ Mailroom version {mailroom.get_client().version()} running at️ {Fore.CYAN}{settings.MAILROOM_URL}{Fore.RESET}"
            )
        except ConnectionError:
            launch = f'mailroom -db="{db_url}" -redis={redis_url}'
            raise CommandError(f"Unable to connect to mailroom. Please launch it with...\n\n{launch}")

        try:
            requests.get(COURIER_URL)
            print(f"✅ Courier running at️ {Fore.CYAN}{COURIER_URL}{Fore.RESET}")
        except ConnectionError:
            launch = f'courier -db="{db_url}" -redis={redis_url} -spool-dir="."'
            raise CommandError(f"Unable to connect to courier. Please launch it with...\n\n{launch}")

        try:
            self.channel = TestChannel.create(
                org, org.administrators.first(), COURIER_URL, callback=self.response_callback, scheme=scheme
            )
            print(f"✅ Testing channel started at️ {Fore.CYAN}{self.channel.server.base_url}{Fore.RESET}")
        except Exception as e:
            raise CommandError(f"Unable to start test channel: {str(e)}")

        print(
            f"\nSending messages to {Fore.CYAN}{org.name}{Fore.RESET} as {Fore.CYAN}{scheme}:{path}{Fore.RESET}. "
            "Use Ctrl+C to quit."
        )

        try:
            while True:
                line = input(self.prompt)
                if not line:
                    continue

                self.channel.incoming(path, line)

        except KeyboardInterrupt:
            pass
Пример #6
0
    def validate_urns(self, value):
        if value is not None:
            self.parsed_urns = []
            for urn in value:
                try:
                    normalized = URN.normalize(urn)
                    scheme, path, display = URN.to_parts(normalized)
                    # for backwards compatibility we don't validate phone numbers here
                    if scheme != TEL_SCHEME and not URN.validate(normalized):  # pragma: needs cover
                        raise ValueError()
                except ValueError:
                    raise serializers.ValidationError("Invalid URN: '%s'" % urn)

                self.parsed_urns.append(normalized)

        return value
Пример #7
0
    def validate_urns(self, value):
        if value is not None:
            self.parsed_urns = []
            for urn in value:
                try:
                    normalized = URN.normalize(urn)
                    scheme, path, query, display = URN.to_parts(normalized)
                    # for backwards compatibility we don't validate phone numbers here
                    if scheme != TEL_SCHEME and not URN.validate(normalized):  # pragma: needs cover
                        raise ValueError()
                except ValueError:
                    raise serializers.ValidationError("Invalid URN: '%s'" % urn)

                self.parsed_urns.append(normalized)

        return value
Пример #8
0
    def send(self, channel, msg, text):
        twitter = TembaTwython.from_channel(channel)
        start = time.time()

        try:
            urn = getattr(msg, 'urn', URN.from_twitter(msg.urn_path))
            (scheme, path, display) = URN.to_parts(urn)

            # this is a legacy URN (no display), the path is our screen name
            if scheme == TWITTER_SCHEME:
                dm = twitter.send_direct_message(screen_name=path, text=text)

            # this is a new twitterid URN, our path is our user id
            else:
                dm = twitter.send_direct_message(user_id=path, text=text)

        except Exception as e:
            error_code = getattr(e, 'error_code', 400)
            fatal = False

            if error_code == 404:  # handle doesn't exist
                fatal = True
            elif error_code == 403:
                for err in self.FATAL_403S:
                    if six.text_type(e).find(err) >= 0:
                        fatal = True
                        break

            # if message can never be sent, stop them contact
            if fatal:
                contact = Contact.objects.get(id=msg.contact)
                contact.stop(contact.modified_by)

            raise SendException(str(e),
                                events=twitter.events,
                                fatal=fatal,
                                start=start)

        external_id = dm['id']
        Channel.success(channel,
                        msg,
                        WIRED,
                        start,
                        events=twitter.events,
                        external_id=external_id)
Пример #9
0
def serialize_contact(contact):
    from temba.contacts.models import URN

    field_values = {}
    for field in contact.org.cached_contact_fields.values():
        field_values[field.key] = contact.get_field_json(field)

    # augment URN values with preferred channel UUID as a parameter
    urn_values = []
    for u in contact.urns.order_by("-priority", "id"):
        # for each URN we resolve the preferred channel and include that as a query param
        channel = contact.org.get_send_channel(contact_urn=u)
        if channel:
            scheme, path, query, display = URN.to_parts(u.urn)
            urn_str = URN.from_parts(scheme,
                                     path,
                                     query=urlencode(
                                         {"channel": str(channel.uuid)}),
                                     display=display)
        else:
            urn_str = u.urn

        urn_values.append(urn_str)

    return {
        "uuid":
        contact.uuid,
        "id":
        contact.id,
        "name":
        contact.name,
        "language":
        contact.language,
        "timezone":
        "UTC",
        "urns":
        urn_values,
        "groups": [
            serialize_group_ref(group)
            for group in contact.user_groups.filter(is_active=True)
        ],
        "fields":
        field_values,
    }
Пример #10
0
def reduce_event(event):
    new_event = copy_keys(event, {"type", "msg"})

    if "msg" in new_event:
        new_event["msg"] = copy_keys(event["msg"], {"text", "urn", "channel", "attachments"})
        new_msg = new_event["msg"]

        # legacy events are re-constructed from real messages which have their text stripped
        if "text" in new_msg:
            new_msg["text"] = new_msg["text"].strip()

        # legacy events have absolute paths for attachments, new have relative
        if "attachments" in new_msg:
            abs_prefix = f"https://{settings.AWS_BUCKET_DOMAIN}/"
            new_msg["attachments"] = [a.replace(abs_prefix, "") for a in new_msg["attachments"]]

        # new engine events might have params on URNs that we're not interested in
        if "urn" in new_msg:
            scheme, path, query, fragment = URN.to_parts(new_msg["urn"])
            new_msg["urn"] = URN.from_parts(scheme, path, None, fragment)

    return new_event
Пример #11
0
    def send(self, channel, msg, text):
        twitter = TembaTwython.from_channel(channel)
        start = time.time()

        try:
            urn = getattr(msg, 'urn', URN.from_twitter(msg.urn_path))
            (scheme, path, display) = URN.to_parts(urn)
            print("scheme %s" % (scheme))
            print("path %s" % (path))
            print("display %s" % (display))
            # this is a legacy URN (no display), the path is our screen name
            if scheme == TWITTER_SCHEME:
                dm = twitter.send_direct_message(screen_name=path, text=text)
                external_id = dm['id']

            # this is a new twitterid URN, our path is our user id
            else:
                metadata = msg.metadata if hasattr(msg, 'metadata') else {}
                quick_replies = metadata.get('quick_replies', [])
                formatted_replies = [
                    dict(label=item[:self.quick_reply_text_size])
                    for item in quick_replies
                ]

                if quick_replies:
                    params = {
                        'event': {
                            'type': 'message_create',
                            'message_create': {
                                'target': {
                                    'recipient_id': path
                                },
                                'message_data': {
                                    'text': text,
                                    'quick_reply': {
                                        'type': 'options',
                                        'options': formatted_replies
                                    }
                                }
                            }
                        }
                    }
                    dm = twitter.post('direct_messages/events/new',
                                      params=params)
                    external_id = dm['event']['id']
                else:
                    print(path)
                    print(text)
                    dm = twitter.send_direct_message(user_id=path, text=text)
                    external_id = dm['id']

        except Exception as e:
            error_code = getattr(e, 'error_code', 400)
            fatal = False
            print("Twitter type")
            print(e)

            if error_code == 404:  # handle doesn't exist
                fatal = True
            elif error_code == 403:
                for err in self.FATAL_403S:
                    if six.text_type(e).find(err) >= 0:
                        fatal = True
                        break

            # if message can never be sent, stop them contact
            if fatal:
                contact = Contact.objects.get(id=msg.contact)
                contact.stop(contact.modified_by)

            raise SendException(str(e),
                                events=twitter.events,
                                fatal=fatal,
                                start=start)

        Channel.success(channel,
                        msg,
                        WIRED,
                        start,
                        events=twitter.events,
                        external_id=external_id)
Пример #12
0
    def handle(self, *args, **options):
        colorama_init()
        org = get_org(options["org"])
        scheme, path, *rest = URN.to_parts(options["urn"])

        db = settings.DATABASES["default"]
        db_url = f"postgres://{db['USER']}:{db['PASSWORD']}@{db['HOST']}:{db['PORT']}/{db['NAME']}?sslmode=disable"
        redis_url = settings.CACHES["default"]["LOCATION"]

        try:
            print(
                f"✅ Mailroom version {mailroom.get_client().version()} running at️ {Fore.CYAN}{settings.MAILROOM_URL}{Fore.RESET}"
            )
        except ConnectionError:
            launch = f'mailroom -db="{db_url}" -redis={redis_url}'
            raise CommandError(
                f"Unable to connect to mailroom. Please launch it with...\n\n{launch}"
            )

        try:
            requests.get(COURIER_URL)
            print(
                f"✅ Courier running at️ {Fore.CYAN}{COURIER_URL}{Fore.RESET}")
        except ConnectionError:
            launch = f'courier -db="{db_url}" -redis={redis_url} -spool-dir="."'
            raise CommandError(
                f"Unable to connect to courier. Please launch it with...\n\n{launch}"
            )

        try:
            channel = TestChannel.create(org,
                                         org.administrators.first(),
                                         COURIER_URL,
                                         callback=self.response_callback,
                                         scheme=scheme)
            print(
                f"✅ Testing channel started at️ {Fore.CYAN}{channel.server.base_url}{Fore.RESET}"
            )
        except Exception as e:
            raise CommandError(f"Unable to start test channel: {str(e)}")

        print(
            f"\nSending messages to {Fore.CYAN}{org.name}{Fore.RESET} as {Fore.CYAN}{scheme}:{path}{Fore.RESET}. Use Ctrl+C to quit."
        )

        self.responses_wait = None
        try:
            while True:
                line = input(f"📱 {Fore.CYAN}{path}{Fore.RESET}> ")
                if not line:
                    continue

                msg_in = channel.incoming(path, line)

                # we wait up to 2 seconds for a response from courier
                self.responses_wait = threading.Event()
                self.responses_wait.wait(timeout=2)

                for response in org.msgs.filter(
                        direction="O", id__gt=msg_in.id).order_by("id"):
                    print(
                        f"💬 {Fore.GREEN}{response.channel.address}{Fore.RESET}> {response.text}"
                    )

        except KeyboardInterrupt:
            pass