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
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, }
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
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)
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
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
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
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)
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, }
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
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)
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