JeanCarl's Adventures

Project 4: Party Texter with PubNub and Tropo

June 19, 2015 |

Since the first time I came across PubNub at a hackathon, I’ve loved how simple it is to send messages across different platforms really quickly. They have libraries for many languages, and the awesome part is that they all work seamlessly together. Once you decide on the type of message content, the sending and receiving applications can be whatever you choose.

In this fourth project of my 15 projects in 30 days challenge, I’m going to use Tropo and PubNub to receive incoming texts and count them in a leaderboard party texting activity.

Here’s how it’s going to work. We’ll set up a phone number with Tropo to recieve text messages, translate them into a PubNub message, and receive them in our AngularJS app. The AngularJS app will keep tally of the number of text messages received per number, show a leaderboard of top texters, and also show text messages as they come in.

Tropo

Tropo is a service that makes it really simple to receive text messages in our app. Sign up for an account at tropo.com. When you set up an application, you can specify an endpoint that is sent a JSON object when a text message is sent to the phone number.

I’m using the theorectical IP address 0.0.0.0 and port 8080 to point to where the Node.js app is running on my server. Change this to point to where you run the app.

Photo

PubNub

PubNub will be used to notify our AngularJS app of the text message received by the Node.js app. Sign up for an account at pubnub.com. You’ll need the publish key and subscribe key when we set up the Node.js app in the next step.

Photo

Node.js

If you haven’t set up Node.js, these commands will get everything installed.

To install Node.js, run the following commands:

sudo apt-get update
sudo apt-get install nodejs
sudo apt-get install npm

Some version info:

# nodejs -v
v0.10.25

# npm -v
1.3.10

This project requires a couple of modules to be installed. Run these two commands to install the express and body-parser node modules.

npm install express
npm install body-parser

Party Texter

Now that we have set up the two APIs and have Node.js installed, we need to deploy the Node.js app. There are three files for this project. server.js will listen for requests with text messages from Tropo and transmit a message to PubNub. Our AngularJS app will subscribe to the PubNub channel and receive messages representing each text message received. index.html and partytexter.js make up the Angular JS app.

// Filename: server.js

// What port to listen to.
var port = 8080;

// PubNub Publish Key
var pubnub_publish_key = 'PUBLISHKEY';

// PubNub Subscribe Key
var pubnub_subscribe_key = 'SUBSCRIBEKEY';

/*********** End Configuration ***********/

var express = require('express');
var app = express();
var bodyParser = require('body-parser');

var pubnub = require('pubnub').init({
    publish_key: pubnub_publish_key,
    subscribe_key: pubnub_subscribe_key
});

app.use(bodyParser.json());
app.listen(port);

console.log("App listening on port "+port);

// Callback handler for Tropo service.
app.post('/api/sms', function(req, res) {
    pubnub.publish({
        channel: 'sms',
        message: {
            from: req.body.session.from.id,
            to: req.body.session.to.id,
            text: req.body.session.initialText
        }
    });

    res.end();
});

app.use(express.static(__dirname + '/public'));
<!-- Filename: public/index.html -->
<html ng-app="PartyTexterApp">
<head>
	<title>Party Texter</title>
	<script src="http://cdn.pubnub.com/pubnub.min.js"></script>
	<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.16/angular.min.js"></script>
    <script src="partytexter.js"></script>
	<style>
	body {
		font-family: Arial;
	}

	.leaderboard {
		border: 1px solid black; 
		float: right; 
		width: 10%;
	}

	.lastmessage {
		width: 75%; 
		text-align: center; 
		border: 1px solid #C0C0C0; 
		background-color: #F0F0F0; 
		padding: 20px; 
		position: relative;
	}

	.lastmessage .number {
		position: absolute; 
		bottom: 5px; 
		right: 5px;
		font-style: italic;
	}

	.lastmessage .text {
		font-size: 36pt; 
	}
	</style>
</head>

<body ng-controller="PartyTexterCtrl">
	<div>
		Text your message to {{phoneNumber}}
	</div>

	<table class="leaderboard" ng-show="lastMessage">
		<tr>
			<th>Phone #</th>
			<th>Score</th>
		</tr>
		<tr ng-repeat="leader in leaderBoard | orderObjectBy:'count' | orderBy:'-count'">
			<td>{{leader.number | phone:6}}</td>
			<td style="text-align: right">{{leader.count}}</td>
		</tr>
	</table>

	<div class="lastmessage" ng-show="lastMessage">
		<span class="number">{{lastMessage.from | phone:6}}</span>
		<span class="text">{{lastMessage.text}}</span>
	</div>
</body>
</html>
// Filename: public/partytexter.js

angular.module('smsListenFilters', [])
.filter('phone', function() {
  return function(input, showLast) {
  	if(!angular.isString(input)) return input;
  	if(!showLast) showLast = input.length;

    return input.substring(0, input.length-showLast).replace(/\d/g, '*')+input.substring(input.length-showLast, input.length);
  };
})
.filter('orderObjectBy', function(){
 return function(input, attribute) {
    if (!angular.isObject(input)) return input;

    var array = [];
    for(var objectKey in input) {
        array.push(input[objectKey]);
    }

    array.sort(function(a, b){
        a = parseInt(a[attribute]);
        b = parseInt(b[attribute]);
        return a - b;
    });
    return array;
 }
});

angular.module('PartyTexterApp', ['smsListenFilters'])
.controller('PartyTexterCtrl', ['$scope', function($scope) {
	$scope.lastMessage = null;
	$scope.leaderBoard = {};
	$scope.phoneNumber = 'PHONENUMBER';

	var incoming = PUBNUB.init({
        subscribe_key: 'SUBSCRIBEKEY'
    });	

    incoming.subscribe({
	    channel: 'sms',
	    message: function(m) {
	    	$scope.$apply(function() {
    			$scope.lastMessage = m;

    			if(!(m.from in $scope.leaderBoard)) {
    				$scope.leaderBoard[m.from] = {number: m.from, count: 0};
    			}

    			$scope.leaderBoard[m.from].count++;
    		});
	    }
	});
}]);

Replace PUBLISHKEY and SUBSCRIBEKEY in server.js with the PubNub Publish Key and Subscribe Key, and SUBSCRIBEKEY in partytexter.js with the PubNub Subscribe Key. Replace PHONENUMBER with the Tropo phone number to text to. It can be in any format you choose as it is only displayed to the user.

Start up the Node.js app:

nodejs server.js

And visit the app at <>/index.html. At first, there isn’t much to see.

Photo

Text a message to your Tropo number. In a few seconds, it should show up.

Photo

As the night goes on with more text messages, a leaderboard will begin to emerge.

Photo

The leaderboard will automatically reorder with the top texter at the top of the list.

Source Code

You can find the repo on GitHub.

That’s it for this project. Here are some ideas to expand:

  • Instead of showing just the last text message, show a list of them. The newest text message appears at the top, and the oldest one disappears.
  • Keep track of when the leader changes, and activate an animation to make it clear the leader has changed.
  • Limit the leaderboard to the top 5 or 10.

Post-mortem

This project was fun because it took text messages, transformed them into PubNub messages, and then made it a game. PubNub is a pretty neat platform to deliver messages across multiple platforms as this project has shown.

This is the first project that uses a custom filter, where I’ve masked some of the digits of the phone number. Pretty simple, but a good start to understanding filters and the power they provide.

15 Projects in 30 Days Challenge

This blog post is part of my 15 projects in 30 days challenge. I’m hacking together 15 projects with different APIs, services, and technologies that I’ve had little to no exposure to. If my code isn’t completely efficient or accurate, please understand it isn’t meant to be complete and bulletproof. When something is left out, I try to mention it. Reach out to me and kindly teach me if I go towards the dark side. ?

This challenge serves a couple of purposes. First, I’ve always enjoyed hacking new things together and using APIs. And I haven’t had the chance (more like a reason) to dive in head first with things like AngularJS, PubNub, and Tropo.