def _send_sms(conversation, user, msg): """ Send an SMS message to the given user within the given conversation. Note that we only send the SMS if the user's phone number has been verified. """ logger.debug('in core.lib.conversationHandler._send_sms(' + 'conversation=%s, user=%s, msg=%s' % (repr(conversation), repr(user), repr(msg))) if user.verified: sending_number = twilio_gateway.calc_sending_phone_number(conversation, user) twilio_gateway.send_sms(sending_number, user.phone_number, msg)
def merge(old_session, new_session): """ Merge two users. """ raise UnauthorizedException() # Disable for now. logger.debug("in core.api.users.merge(" + "old_session=%s, new_session=%s)" % (repr(old_session), repr(new_session))) # Check that the session parameters are correct. We require two valid # session IDs, the old one for an ad hoc user, and the new one for a non-ad # hoc user. if old_session == None or new_session == None: raise InvalidParametersException() sessionHandler.validate(old_session) sessionHandler.validate(new_session) old_user = sessionHandler.get_user(old_session) new_user = sessionHandler.get_user(new_session) if not old_user.ad_hoc or new_user.ad_hoc: raise UnauthorizedException() # Build a list of the old user's conversations. old_conversations = [] for conversation in Conversation.objects.filter(user_1=old_user): old_conversations.append(conversation) for conversation in Conversation.objects.filter(user_2=old_user): if conversation.user_1 != old_user: old_conversations.append(conversation) # Build a list of the old user's active conversations. Note that we only # store the ID of the other user and the topic ID for the conversation, as # the Conversation object itself may get deleted as part of the merge # process. We'll need this to send an SMS to the new user telling them # that they've taken the conversation mobile. active_conversations = [] # List of (other_user_id, topic_id) tuples. for conversation in Conversation.objects.filter(user_1=old_user): if not conversation.stopped: active_conversations.append((conversation.user_2.id, conversation.topic.id)) for conversation in Conversation.objects.filter(user_2=old_user): if not conversation.stopped and conversation.user_1 != old_user: active_conversations.append((conversation.user_1.id, conversation.topic.id)) # Build a list of the new user's conversations. new_conversations = [] for conversation in Conversation.objects.filter(user_1=new_user): new_conversations.append(conversation) for conversation in Conversation.objects.filter(user_2=new_user): if conversation.user_1 == new_user: new_conversations.append(conversation) # Change each of the old conversations to belong to the new user. If the # updated conversation is a duplicate of an existing conversation for the # new user, we have to delete the old conversation and update the # associated messages. for old_conversation in old_conversations: if old_conversation.user_1 == old_user: other_old_user = old_conversation.user_2 else: other_old_user = old_conversation.user_1 is_duplicate = False for new_conversation in new_conversations: if new_conversation.user_1 == new_user: other_new_user = new_conversation.user_2 else: other_new_user = new_conversation.user_1 if other_old_user == other_new_user: is_duplicate = True break if is_duplicate: # Update the messages for old_conversation to be part of the # new_conversation instead, and delete the old conversation. Message.objects.filter(conversation=old_conversation).update( conversation=new_conversation) old_conversation.delete() else: # Change the old_conversation to belong to the new_user instead of # the old_user. if old_conversation.user_1 == old_user: old_conversation.user_1 = new_user else: old_conversation.user_2 = new_user old_conversation.save() # Update the old user's messages to refer to the new user. Message.objects.filter(sender=old_user).update(sender=new_user) # Update any topics owned by the old user to now be owned by the new user. # Note that we deliberately avoid overwriting the default and any existing # named topics for the new user. topics = [] for topic in Topic.objects.filter(user=old_user): topics.append(topic) for topic in topics: if topic.default: continue # Don't copy across default topic. if Topic.objects.filter(user=new_user, name=topic.name): continue # Don't overwrite existing named topic. topic.user = new_user topic.save() # Update any SMS channels which sent messages to the old user to now send # messages to the new user. channels = [] for channel in TwilioSMSChannel.objects.filter(sender=old_user): channels.append(channel) for channel in channels: if TwilioSMSChannel.objects.filter(conversation=channel.conversation, recipient=channel.recipient, phone_number=channel.phone_number, sender=new_user).exists(): continue # Don't overwrite existing SMS channel. channel.sender = new_user channel.save() # Update any SMS channels which sent messages from the old user to now send # messages from the new user. channels = [] for channel in TwilioSMSChannel.objects.filter(recipient=old_user): channels.append(channel) for channel in channels: if TwilioSMSChannel.objects.filter(conversation=channel.conversation, sender=channel.sender, phone_number=channel.phone_number, recipient=new_user).exists(): continue # Don't overwrite existing SMS channel. channel.recipient = new_user channel.save() # Finally, if the new user has a verified phone number, send a message to # the new user for each active conversation, telling the user that they # have "taken the conversation mobile." if new_user.verified: for other_user,topic in active_conversations: try: conversation = Conversation.objects.get(user_1=new_user, user_2__id=other_user, topic=topic) except Conversation.DoesNotExist: conversation = Conversation.objects.get(user_1__id=other_user, user_2=new_user, topic=topic) sending_phone_number = twilio_gateway.calc_sending_phone_number( conversation, new_user) msg = settings.SMS_TEXT_CONVERSATION_TAKEN_MOBILE twilio_gateway.send_sms(sending_phone_number, new_user.phone_number, msg)
def test_stop_via_sms(self): """ Test the stopping of a conversation via sms message. """ # Create two users, giving them a phone number so they can receive # messages. username_1 = utils.random_username() password_1 = utils.random_password() username_2 = utils.random_username() password_2 = utils.random_password() user_1 = users.create(username=username_1, password=password_2, phone_number=PHONE_NUMBER) user_2 = users.create(username=username_2, password=password_2, phone_number=PHONE_NUMBER_2) # Verify both phone numbers. user = User.objects.get(id=user_1['id']) user.verified = True user.save() user = User.objects.get(id=user_2['id']) user.verified = True user.save() # Log in as user 2, and create a dummy topic for this user. session = users.login(username=username_2, password=password_2) topic = topics.create(session, topic_name=None) # Log in as user 1, and send a test message to the topic. This # creates a conversation between the two users. session = users.login(username=username_1, password=password_1) with self.settings(ENABLE_TWILIO=False, ENABLE_PUBNUB=False): message = messages.send(session, topic_id=topic['id'], message=MESSAGE_BODY) # Get the created conversation. conversation = conversationHandler.get(user_1['id'], user_2['id'], topic['id']) # Set up a signal listener to check that the "conversation has stopped" # messages were sent out. self.twilio_sms_messages = [] def twilio_signal_handler(sender, **kwargs): self.twilio_sms_messages.append(kwargs.get("message")) signals.twilio_sms_sent.connect(twilio_signal_handler) # Ask the twilio gateway to calculate the phone number to use for # sending SMS messages to user 2. This opens up an SMS channel for the # conversation we're pretending to have. sending_phone_number = \ twilio_gateway.calc_sending_phone_number(conversation, user_2) # Simulate an incoming SMS reply being received from user 2, containing # the word "stop". Note that we disable Twilio and PubNub so that # nothing will actually get sent out. with self.settings(ENABLE_TWILIO=False, ENABLE_PUBNUB=False): response = messages.receive(To=sending_phone_number, From=user_2.phone_number, Body="stop") # Check that the Twilio gateway sent the "conversation has been # stopped" message to both parties. self.assertEqual(len(twilio_sms_messages), 2) # Finally, clean everything up. signals.twilio_sms_sent.disconnect(twilio_signal_handler)
def send_message(sender, recipient, topic, sender_name, message_text): """ Create and send a new message. The parameters are as follows: 'sender' The User object for the message's sender. 'recipient' The User object for the message's recipient. 'topic' The Topic object that this message is about. 'sender_name' The optional name for the sender of the message. 'message_text' The text of the message. We send the given message from the given sender to the given recipient, about the given topic. Note that we'll automatically create a conversation (and send an "intro" message") if one doesn't already exist. Upon completion, we return the newly-created Message object, or None if the conversation has been stopped. If the message recipient has reached their rate limit, we raise a RateLimitReachedException. """ logger.debug('in core.lib.messageHandler.send_mesage(' + 'sender=%s, recipient=%s, topic=%s, sender_name=%s, msg=%s)' % (repr(sender), repr(recipient), repr(topic), repr(sender_name), repr(message_text))) # Find the Conversation object to use for this message, creating it if # necessary. try: conversation = conversationHandler.get(sender.id, recipient.id, topic.id) except NoSuchConversationException: conversation = conversationHandler.start_conversation(sender.id, recipient.id, topic.id, sender_name) # If the conversation has been stopped, don't send out the message. if conversation.stopped: return None # See if we can send this message via SMS. We do this if the receipient # has a verified phone number. send_via_sms = False # initially. if recipient.phone_number not in ["", None] and recipient.verified: send_via_sms = True # Create the new Message object. message = Message() message.conversation = conversation message.sender = sender message.sender_name = sender_name message.body = message_text message.sent_via_sms = send_via_sms message.created_at = datetime.datetime.utcnow() message.updated_at = datetime.datetime.utcnow() message.save() # Send the message via PubNub. pubnub_gateway.send_notification(message) pubnub_gateway.send_message_to_firehose(message) # Send the message via Twilio, if we can. Note that we append the sender's # name (if any) to the outgoing SMS message. if send_via_sms: sending_phone_number = \ twilio_gateway.calc_sending_phone_number(conversation, recipient) if sender_name not in ["", None]: message_text = message_text + " [" + sender_name + "]" twilio_gateway.send_sms(sending_phone_number, recipient.phone_number, message_text) # Finally, return the newly-created message back to the caller. return message
def test_receive(self): """ Test messages/receive. """ # Create the two users we'll need for our test. user_1_username = utils.random_username() user_1_password = utils.random_password() user_1_id = users.create(username=user_1_username, password=user_1_password, phone_number=PHONE_NUMBER)['id'] user_2_id = users.create(phone_number=PHONE_NUMBER_2)['id'] user_1 = User.objects.get(id=user_1_id) user_2 = User.objects.get(id=user_2_id) # Verify user 1's phone number, so that replies will be forwarded to # that number. user_1.verified = True user_1.save() # Log in as user 1, and create a topic for this user. We'll need this # topic for our pseudo-conversation. session = users.login(username=user_1_username, password=user_1_password) topic_id = topics.create(session, topic_name=None)['id'] topic = Topic.objects.get(id=topic_id) # Create our pseudo-conversation. conversation = Conversation() conversation.user_1 = user_1 conversation.user_2 = user_2 conversation.topic = topic conversation.save() # Ask the twilio gateway to calculate the phone number to use for # sending SMS messages to user 2. This opens up an SMS channel for the # conversation we're pretending to have. sending_phone_number = \ twilio_gateway.calc_sending_phone_number(conversation, user_2) # Set up a signal listener to check that the reply was forwarded via # Twilio. self.twilio_messages = [] def twilio_signal_handler(sender, **kwargs): self.twilio_messages.append(kwargs.get("message")) signals.twilio_sms_sent.connect(twilio_signal_handler) # Set up a signal listener to check that the reply was forwarded via # PubNub. self.pubnub_notification_sent = False # initially. self.pubnub_message = None # ditto. def pubnub_signal_handler(sender, **kwargs): self.pubnub_notification_sent = True self.pubnub_message = kwargs.get("message") signals.pubnub_notification_sent.connect(pubnub_signal_handler) # Simulate an incoming SMS reply being received from user 2. Note that # we disable Twilio and PubNub so that the SMS reply isn't actually # sent out to the original sender. with self.settings(ENABLE_TWILIO=False, ENABLE_PUBNUB=False): response = messages.receive(To=sending_phone_number, From=user_2.phone_number, Body=REPLY_BODY) # Check that the response is what we expect. self.assertEqual(response.status_code, 201) self.assertEqual(response['Content-Type'], "text/xml") # Check that the Twilio gateway sent the message. if len(self.twilio_messages) == 0: # Whoops! The reply wasn't forwarded, which means that something # went seriously wrong. Print out the response so we can see the # XML message sent back to Twilio. print "Error response sent to Twilio:" print response.content self.fail("Reply not forwarded -> something went wrong.") # Check that the PubNub gateway sent the notification. self.assertTrue(self.pubnub_notification_sent) self.assertEqual(self.pubnub_message.body, REPLY_BODY) self.assertEqual(self.pubnub_message.conversation.topic.id, topic_id) # Finally, clean everything up. signals.twilio_sms_sent.disconnect(twilio_signal_handler) signals.pubnub_notification_sent.disconnect(pubnub_signal_handler)