Get NBA scores on your phone with the new Nexmo Messages API
The NBA’s 2018-19 season just started and the number of stories to follow this year is through the roof. Is Lebron enough to make the Lakers a real contender in the West? Is Kawhi going to be healthy enough to get the Raptors to the NBA finals or even win the title? Are the Warriors going to dominate the NBA once again? Let’s be real, that last one is probably not even a valid question. The truth is that the NBA has a flavor for everybody and this year seems to be one of the most exciting since David Robinson and Tim Duncan dominated the NBA. Yes, Spurs fan revealed.
Anyhow, if you are passionate about the NBA and you want to develop a new coding skill to go along with it, this blog is right for you. We are going to use the Nexmo Messages API (in beta), the https://stats.nba.com API and some small chunks of JavaScript code to implement a simple Node JS application that notifies you of your favorite team’s scores via an SMS message.
Configuring Nexmo Account
The first thing we need to do is register for a Nexmo account. Have your phone at hand since you need an SMS that is sent to verify your account
Once you register and verify your account, you can go to your dashboard. In the getting started page take note of your Key and Secret, you will need those later. Then, you need to create an application, which can be done clicking on the Messages and Dispatch API sidebar menu and Create Application. Set your application name and, for now, you can enter a sample status and inbound urls. Also, for the purposes of this tutorial, you are going to generate public and private keys using the Generate public/private key pair functionality. Remember where you store the private.key file since you are going to use it later. Skip the external accounts step and take note of your Application ID. After getting your Key and Secret there is a final configuration step before getting your hands on the code. You need to install Node JS, if you haven’t installed it already. When Node JS is installed you can get started.
Quick note: you can find the final version of this code in this github repository.
Configuring the app
Create a new directory for the application. You can call it whatever you want but for our purposes we are going to call it app . On app we initialize our application by running.
> npm init -y
This will create a file named package.json that should look something like this. Note that we changed the author:
{ name: "app", version: "1.0.0", description: "", main: "index.js", scripts: { test: "echo \"Error: no test specified\" && exit 1" }, keywords: [], author: "WebRTC Ventures", license: "ISC" }
Finally we are going to install the Nexmo Node Js library and two external applications: axios to help us to query the NBA stats API using HTTP requests and node-schedule to schedule the notifications on a daily basis. This can be done by running the following npm command:
> npm install nexmo@beta axios node-schedule
This command will do two visible actions. It will create a node_modules directory on app and then add a dependencies section on our package.json, which allows us to install the application in future environments.
If you take a closer look at package.json, on the main tag it says that the main file to execute is index.js, which we haven’t created. So, go ahead and create this file under the app directory. Also, we are going to create two additional files that we are going to use later called parameters.js and variables.js . On parameters.js we are going to add all the information needed to connect with the NBA stats API and on variables.js we are going to add all configuration information needed by Nexmo to send the SMS messages.
Setting up to use NBA stats API and Nexmo Messages API
To set up the information needed to connect to the NBA stats API, please copy the following code to the parameters.js:
module.exports = { url: "https://stats.nba.com/stats/", teamEndpoint: "teamgamelog", gameEndpoint: "boxscoresummaryv2", teams: [ {id: 1, city: "Atlanta", name: "Hawks", apiId: "1610612737"}, {id: 2, city: "Boston", name: "Celtics", apiId: "1610612738"}, {id: 3, city: "Brooklyn", name: "Nets", apiId: "1610612751"}, {id: 4, city: "Charlotte", name: "Hornets", apiId: "1610612766"}, {id: 5, city: "Chicago", name: "Bulls", apiId: "1610612741"}, {id: 6, city: "Cleveland", name: "Cavaliers", apiId: "1610612739"}, {id: 7, city: "Dallas", name: "Mavericks", apiId: "1610612742"}, {id: 8, city: "Denver", name: "Nuggets", apiId: "1610612743"}, {id: 9, city: "Detroit", name: "Pistons", apiId: "1610612765"}, {id: 10, city: "Golden State", name: "Warriors", apiId: "1610612744"}, {id: 11, city: "Houston", name: "Rockets", apiId: "1610612745"}, {id: 12, city: "Indiana", name: "Pacers", apiId: "1610612754"}, {id: 13, city: "Los Angeles", name: "Clippers", apiId: "1610612746"}, {id: 14, city: "Los Angeles", name: "Lakers", apiId: "1610612747"}, {id: 15, city: "Memphis", name: "Grizzlies", apiId: "1610612763"}, {id: 16, city: "Miami", name: "Heat", apiId: "1610612748"}, {id: 17, city: "Milwaukee", name: "Bucks", apiId: "1610612749"}, {id: 18, city: "Minnesota", name: "Timberwolves", apiId: "1610612750"}, {id: 19, city: "New Orleans", name: "Pelicans", apiId: "1610612740"}, {id: 20, city: "New York", name: "Knicks", apiId: "1610612752"}, {id: 21, city: "Oklahoma City", name: "Thunder", apiId: "1610612760"}, {id: 22, city: "Orlando", name: "Magic", apiId: "1610612753"}, {id: 23, city: "Philadelphia", name: "76ers", apiId: "1610612755"}, {id: 24, city: "Phoenix", name: "Suns", apiId: "1610612756"}, {id: 25, city: "Portland", name: "Trail Blazers", apiId: "1610612757"}, {id: 26, city: "Sacramento", name: "Kings", apiId: "1610612758"}, {id: 27, city: "San Antonio", name: "Spurs", apiId: "1610612759"}, {id: 28, city: "Toronto", name: "Raptors", apiId: "1610612761"}, {id: 29, city: "Utah", name: "Jazz", apiId: "1610612762"}, {id: 30, city: "Washington", name: "Wizards", apiId: "1610612764"} ], season: "2018-19", seasonType: { regular: "Regular Season", preseason: "preseason", playoffs: "Playoffs", allstar: "All-Star" } }
A quick explanation of what every parameter means in this file:
- url: this is the actual url we are going to call to get the information from the NBA stats API.
- teamEndpoint: this is the endpoint used to get the information about the games a team has played on a specific date range.
- gameEndpoint: this endpoint returns information about an actual game, we use it to get the points scored by both teams.
- teams: this is a list of teams that has a friendly numerical id, the team’s city, name and apiId used to request information from the teamEndpoint.
- season: this is the season we are querying results from.
- seasonType: this specifies the different types of season we could query.
Now we need to set up our own variables corresponding to our Nexmo account. For that, copy the following code on variables.js:
module.exports = { notificationTime: "08:30", // The Format should be HH:mm. ie, 2 PM should be "14:00" teamToFollowId: 27, // Please look the desired id below. phoneNumber: "50688888888", // The Format should be (CountryCode)Phone Number, no dashes nor + at the start nexmoAPIkey: "", // Found on your Nexmo Dashboard nexmoSecret: "", // Found on your Nexmo Dashboard applicationId: "", // The applicationId of your Nexmo application privateKey: "", // The path to the private.key file of your Nexmo application. }
Remember the Key and Secret we got from the Nexmo dashboard? This is the place to enter that information in the nexmoAPIkey and nexmoSecret variables. The applicationId and privateKey will hold the values for the Application ID value and private.key path for the application you created. On notificationTime we can configure when to receive the notifications, you possibly want this to be early since we are going to retrieve the scores for the previous day. Change the teamToFollowId to determine which team you want to follow, this id is the same as the id field on the parameters teams, so in this case 27 corresponds to the San Antonio Spurs(why of course). Finally, the phoneNumber variable corresponds to the same phone number you used when you registered with Nexmo.
Getting NBA stats information
In order to query the NBA stats API we need to do it via HTTP, this is where axios comes into action. First thing we are going to do is to require those modules on our index.js file
const axios = require ('axios'); const parameters = require ('./parameters');
We are going to work with async functions to modularize the code. Let’s create the functions to get the team’s information. getTeamData which will receive the team id and date as parameters and will use this information in conjunction with axios to query the NBA stats API and getData. That for now its only job will be to call the getTeamData function. At the end we are going to execute getData to see our results in console.
For now let’s just add this after the requires:
async function getTeamData (teamId, date) { let teamData; return teamData; } async function getData() { let teamData = await getTeamData(27, '10/22/2018'); console.log(teamData); } getData();
A couple of important things to notice here:
- We need an async function in order to wait for the execution of another async function.
- We are calling getTeamData using the Spurs id (27) and the date of Oct 22nd, glorious day when they beat the Lakers 143 to 142 on overtime.
If you run our code now, you should get the following result printed on the console:
> node index.js undefined
Now we are going to add some functionality into the getTeamData function. To get the data that we want, we need to call the NBA stats API using something like the following query:
https://stats.nba.com/stats/teamgamelog/?TeamID=1610612759&Season=2018-19&SeasonType=Regular Season&DateFrom=10/22/2018&DateTo=10/22/2018
You can go ahead and copy and paste that into your browser, you’ll see that you get data from that game where the Spurs played against the Lakers. Also, don’t worry I’ll explain what that url means. The first part https://stats.nba.com/stats/teamgamelog/ is just the endpoint that tells the API to please give us team information, then we need to send some parameters to tell the API more specifically what we want. TeamID=1610612759 is the team identification, you can find all team identifications on the parameters.js file, Season=2018-19 and SeasonType=Regular Season are required parameters that basically tells the api that we want the result for the 2018-19 regular season. Finally, DateFrom=10/22/2018 and DateTo=10/22/2018 tell the API which dates we want to request, the format is month/day/year and we are using the same from and to date to get the results just for that day.
Let’s use our parameters to abstract the query for any team. We can do that by adding code to the getTeamData function right before the return statement:
let { url, teamEndpoint, season, seasonType, teams} = parameters; let team = teams.find(team => team.id === teamId); let apiUrl = ( `${url}${teamEndpoint}/?` + `TeamID=${team.apiId}` + `&Season=${season}&SeasonType=${seasonType.regular}` + `&DateFrom=${date}&DateTo=${date}` ); teamData = apiUrl;
We assigned the apiUrl to the teamData just to get it to print the result on console, if you run the code now, you should get something like this:
> node index.js https://stats.nba.com/stats/teamgamelog/?TeamID=1610612759&Season=2018-19&SeasonType=Regular Season&DateFrom=10/22/2018&DateTo=10/22/2018
With the url in place, is time to use axios to request the information from the NBA stats API, to do that lets replace the teamData = apiUrl; line with the following code:
await axios.get(apiUrl) .then(function (response) { let data = response. data .resultSets[0]; if (data && data.rowSet && data.rowSet.length > 0) { teamData = data; } else { console.log("The team didn't play on the date"); } }) .catch(function (error) { console.log('Error getting the team data', error.message); });
This code requests the information to the NBA stats API and if there is any result returned then it sets the teamData to that info, it also adds a log in case there is an error with the request. If we run the code now it should show something like this, (I deleted some information that we are not going to use):
> node index.js { name: 'TeamGameLog', headers: [ 'Team_ID', 'Game_ID', 'GAME_DATE', 'MATCHUP', 'WL', ... ], rowSet: [ [ 1610612759, '0021800048', 'OCT 22, 2018', 'SAS @ LAL', 'W', ... ] ] }
Basically the only thing we need here is the Game_ID which in this case is 0021800048, which we are going to use to get the game information from the NBA stats API. So, we are going to create a new async function called getGameData that help us with that. This function also uses axios to call the API and looks like this:
async function getGameData (gameId) { let gameData; let { url, gameEndpoint } = parameters; let apiUrl =( `${url}${gameEndpoint}/?GameId=${gameId}&` + `StartPeriod=1&EndPeriod=4&StartRange=0&EndRange=10&RangeType=1` ); await axios.get(apiUrl) .then(function (response) { let data = response.data.resultSets[5]; // This is the one with the scores if (data && data.rowSet && data.rowSet.length > 0) { gameData = data; } else { console.log("The team didn't play on the date"); } }) .catch(function (error) { console.log('Error getting the game data', error.message); }); return gameData; }
This function looks really similar to the getTeamData function. There is two things to notice about it. First is that the url is different, it uses the gameId as parameter, we will look how to get this below and it assigns the game data to the sixth result of the resultSets, this is because the endpoint returns a lot of information but the one that we need is the game result, which is the sixth result.
We also need to change our getData function to look like this:
async function getData() { let teamData = await getTeamData(27, '10/22/2018'); if (teamData) { let gameData = await getGameData(teamData.rowSet[0][1], '10/22/2018'); console.log(gameData); } }
Notice that we use teamData.rowSet[0][1] to get the gameId used on getGameData. Now if we run our application we have something like this (again, I deleted some information that we don’t use):
> node index.js { name: 'LineScore', headers: [ ... , 'GAME_ID', 'TEAM_ID', 'TEAM_ABBREVIATION', 'TEAM_CITY_NAME', 'TEAM_NICKNAME', ... , 'PTS' ], rowSet: [ [ ... , '0021800048', 1610612747, 'LAL', 'Los Angeles', 'Lakers', ... , 142 ], [ ... , '0021800048', 1610612759, 'SAS', 'San Antonio', 'Spurs', ... , 143 ] ] }
Formatting the information and sending the SMS
Now we have the data that we actually want. The next step is to create a friendly message to send. We want something like “San Antonio Spurs won against the Los Angeles Lakers 143 to 142”. So, let’s go ahead and create the function generateScoreMessage that receives the game data, the team and a parameter indicating if the team won or lost.
function generateScoreMessage(gameData, teamId, win) { const pos = { city: 5 , name: 6 , points: 22 }; let team = parameters.teams.find(team => team.id === teamId); let userTeamIndex = gameData.rowSet[0][3].toString() === team.apiId ? 0 : 1; let userTeamData = gameData.rowSet[userTeamIndex]; let otherTeamData = gameData.rowSet[1 - userTeamIndex]; let teamName = (t) => `${t[pos.city]} ${t[pos.name]}` ; return ( `${teamName(userTeamData)} ` + (win ? 'won' : 'lost') + ' against the ' + `${teamName(otherTeamData)} ` + `${userTeamData[pos.points]} to ${otherTeamData[pos.points]}` ); }
This function uses a variable called pos which defines where the data that we want is in the row set returned by the NBA stats API, which in turn determines which of the row sets belongs to the user’s team and then generates a message using this information.
And of course we need to change our getData function to use this functionality.
async function getData() { let teamData = await getTeamData(27, '10/22/2018'); if (teamData) { let gameData = await getGameData(teamData.rowSet[0][1], '10/22/2018'); let message = generateScoreMessage(gameData, 27, teamData.rowSet[0][4] === 'W'); console .log(message); } }
The thing to notice here is that we use teamData.rowSet[0][4] === ‘W’ to know if the team won or lost the game and use that information to create the correct message. The result of running the app right now should be:
> node index.js San Antonio Spurs won against the Los Angeles Lakers 143 to 142
So there are just a couple of things missing. First, we need to remove the hard coded parameters. Second, we need to actually send the SMS message and finally we need to schedule the application to send the message at a certain time.
To remove the hard coded parameters we are going to require the variables on the top of our index.js file.
const variables = require('./variables');
Then we need to change the getData function to use the variables and automatically get the day before date. Which looks like this:
async function getData() { let date = new Date (); date.setDate(date.getDate()-1); let queryDate = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; let teamId = variables.teamToFollowId; let teamData = await getTeamData(teamId, queryDate); if (teamData) { let gameData = await getGameData(teamData.rowSet[0][1], queryDate); let message = generateScoreMessage(gameData, teamId, teamData.rowSet[0][4] === 'W'); console.log(message); } }
Notice that if our team didn’t play yesterday we are not going to see the message, instead we are going to see a “The team didn’t play on the date” message. If you want to test the code you just need to change the team on the variables.js file to one that played yesterday.
To implement the second step, sending the SMS, we are going to use the Nexmo API. for that we are going to create a new function called sendMessage that will receive the message we want to send.
async function sendMessage(message) { let number = variables.phoneNumber; const nexmo = new Nexmo({ apiKey: variables.nexmoAPIkey, apiSecret: variables.nexmoSecret, applicationId: variables.applicationId, privateKey: variables.privateKey }); nexmo.channel.send( { "type": "sms", "number": number }, { "type": "sms", "number": number }, { "content": { "type": "text", "text": message } }, (err, data) => { if (err) { console .log(err); } else { console.log('Message sent successfully'); } } ); }
On this function you can see that we are using the Nexmo Node JS library to send the message, also on the data object we configure which kind of message we want to send (in this case this will be a SMS message) the number that we want to send it to and the content of the message.
Of course we want to modify the getData function to include the new functionality.
async function getData () { let date = new Date (); date.setDate(date.getDate()-1 ); let queryDate = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` ; let teamId = variables.teamToFollowId; let teamData = await getTeamData(teamId, queryDate); if (teamData) { let gameData = await getGameData(teamData.rowSet[0][1], queryDate); let message = generateScoreMessage(gameData, teamId, teamData.rowSet[0][4] === 'W'); sendMessage(message); } }
If you run your application, using a team that played yesterday, now you should see the following text in your console:
> node index.js Message sent successfully
And on your phone something like this:
Success!!!!, the final piece of the app is to implement a scheduling job that runs every day at the hour established on variables.js. For this we need to require node-scheduling and replace the call to getData to only be called when the scheduler says so.
const schedule = require('node-schedule');
function scheduleNotifications() { let time = variables.notificationTime.split(":"); if (time.length === 2 && !isNaN(time[0]) && !isNaN(time[1]) ) { let hour = time[0]; let minutes = time[1]; console.log(`Scheduling notifications everyday at ${hour}:${minutes}`); schedule.scheduleJob({hour: hour, minute: minutes}, () => { getData(variables.teamToFollowId); }); } else { console.log("please enter the date on HH:mm format"); } } scheduleNotifications();
So now when we run our application we are going to see the following message:
> node index.js Scheduling notifications everyday at 08:30
And you can expect to receive a notification on your phone every day if your team played the previous day.
Conclusions
In this tutorial we barely scratched the surface of the Nexmo Messages API and we could verify its simplicity. Also it is very flexible, being a REST API it can be called using any platform, framework or programming language you prefer. If you want to learn more about this amazing API, you can check the tutorials on their page. I hope you enjoyed this reading.
Until next time…
Carlos Aguilar
Web Developer @WebRTC Ventures