Cisco Spark bot for monitoring Spark bots. Say whaaat!

The last few weeks I have been working on and testing a new Cisco Spark bot that monitors all my other bots using a RESTful API. Its a relatively simple bot and thanks to an existing Nodejs project from Qawelesizwe Mlilo that was built  to monitor websites I was able to make a few simple changes so it is now a Spark bot that monitors other bots using a RESTful API. Qawelesizwe original project is called node-ping and is posted on GitHub. It has a number of files but the main module is node-monitor which is its own node module available on NPM. I made changes to a couple of the files on the original project with the largest change being removal of the email module to replace with my own Spark message module.

How it Works
This bot works by monitoring a website through the HTML status codes which it pings using a time interval(this is set in the websites.json file). This is very handy because if you are using Express with Nodejs all the status codes are taken care of for you, all you need to do is build a new route on your bot to handle the inquiry from the monitor bot(see the code later in the post). The monitor bot looks at the status code response and adjusts the status of you monitored bot accordingly and sends a Spark message when it changes. If you already have an existing website for your bot that is hosted with the same bot application you could use that as well, but I choose to create a new route so in the future I can deliver more JSON data with my response for future features.


Coding the Monitor Bot
The changes I made to the original node-monitor module are pretty minor. I added a new attribute to the constructor to describe the current bot state. I defaulted all bots to down state. Below is a exert of node-monitor file showing the constructor change.

function Monitor (opts) {
// default http request method
this.method = 'GET';
// holds website to be monitored
this.website = '';
//Track status
this.webStatus = 'down';
// ping intervals in minutes
this.interval = 15;
// interval handler
this.handle = null;
// initialize the app
this.init(opts);
}
Once this was done I took the rest of the node-ping sample code and added the ability to send Spark messages using a bot account instead of using email(boo, no one likes more email) and also adjusted the events to relay up and down status so when a bot comes back up the monitor bot lets me know and stops sending me Spark messages.

"use strict";
var config = require('./config.json');
var message = require('./myUtils/sparkMessage');
var botToken = "<Your bot token>";
/*
Handles events emitted when a websites stop being monitored
@param - (String) website - website url
*/
function onStop (website) {
var to = config.email;
var text = website + ' monitor has stopped';
console.log(JSON.stringify(website))
if(this.webStatus==='up'){
this.webStatus = 'down';
message.newMessage(botToken,text,to);
}else{
message.newMessage(botToken,text,to);
}
};
function onUp (res) {
var to = config.email;
console.log(JSON.stringify(res))
var text = res.website + ' monitor is up';
text += '<p>Time: ' + res.time;
text += '</p><p>Website: ' + res.website;
text += '</p><p>Message: ' + res.statusMessage + '</p>';
if(this.webStatus==='down'){
this.webStatus = 'up';
message.newMessage(botToken,text,to);
}else{
return console.log(this.webStatus);
}
};
/*
Handles events emitted when a website is down
@param - (Object) res - response object return by the Node Monitor object
*/
function onDown (res) {
var text = '';
var to = config.email;
text += '<p>Time: ' + res.time;
text += '</p><p>Website: ' + res.website;
text += '</p><p>Message: ' + res.statusMessage + '</p>';
if(this.webStatus==='up'){
this.webStatus = 'down';
message.newMessage(botToken,text,to);
}else{
this.webStatus = 'down';
message.newMessage(botToken,text,to);
}
};
/*
Handles events emitted when aa error occurs
@param - (String) msg - response message
*/
function onError (msg) {
console.log(msg);
}
module.exports.onStop = onStop;
module.exports.onDown = onDown;
module.exports.onError = onError;
module.exports.onUp = onUp;
Sending a Spark message instead of an email used the file below. Make sure to place in your own bot token and adjust the events.js file for where you place this file.

var request = require('request');
var logger = require('log4js');
logger.configure({
appenders: [
{type:'console'},
{type: 'file', filename: 'logs/application.log', catergory:'application'}
],
replaceConsole: true
});
exports.newMessage = function sparkMessage(sparkToken, text, to) {
request({
url: 'https://api.ciscospark.com/v1/messages',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + sparkToken
},
body:
JSON.stringify({'toPersonEmail': to,
'markdown': text})
}, function (error, response, body) {
if (error) {
console.log(error);
} else {
console.log(response.statusCode, body);
}
});
};
The final change for the bot monitor application was adjusting the server.js file to add the new event for when a monitored bot comes back online.

"use strict"
var Monitor = require('ping-monitor');
var websites = require('./websites');
var http = require('http');
var port = process.env.PORT || 3008;
var events = require('./events');
var logger = require('log4js');
logger.configure({
appenders: [
{type:'console'},
{type: 'file', filename: 'logs/application.log', catergory:'application'}
],
replaceConsole: true
});
var urls = [];
var monitors = [];
/*
Loop over all websites and create a Monitor instance for each one.
*/
websites.forEach(function (website) {
var monitor = new Monitor ({
website: website.url,
interval: website.interval
});
monitor.on('error', events.onError);
monitor.on('stop', events.onStop);
monitor.on('down', events.onDown);
monitor.on('up', events.onUp);
urls.push(website.url);
monitors.push(monitor);
});
/*
Server for responding to http requests
*/
http.createServer(function (req, res) {
res.end(urls.join('\n'));
}).listen(port);
console.log('Listening to port %s', port);
The last change you will need to make is on the bot you want to monitor. If your using Express see below. The change involves setting up a new route to respond to the request from the bot monitor.Seeing as you may or may not use an actual website to monitor I created a REST response. In my case I give a quick JSON response but in future versions I am planning on generating some data to respond with, like rooms configured and up time stats that can be logged.

//Monitoring service route
app.get('/monitor', function(req, res){
var json_response = {'name':'JabberAssist'};
res.status(200).json(json_response);
});
This is just the start of my monitor bot which as you can see has some rough edges but it works and just today it alerted me to a down bot which I had to address. One thing I want to do in the future is a daily bot monitor report delivered in Spark to let me know how my other bots are doing. Right now I have no way of knowing if the monitor bot has gone down so a daily report will help there as well, unless I build a bot to monitor the bot that is monitoring all my other bots, but then how would I know if that bot then went down. I know another bot........

If you want to check out the original Monitor module here is a blog post that describes it. Also here is the GitHub site.

VoIPNorm

XMPP to Spark Bot - Presence Upgrade and Simplification!

Last September I first published my XMPP to Spark bot based on Nodejs that I created to consolidate my collaboration tools. Over the holiday break I made some upgrades and also simplified or I should say reduced the amount of code and modules required. The official Cisco Spark SDK is pretty large and bulky for the purposes of this bot so in the new version I have created a couple of simpler methods to enable its removal. This bot doesn't use webhooks and a bunch of other Spark API's so it was much easier to just create a couple of new methods. This bot is about getting others to use Spark versus a migration or integration tool but with expanded development it could be made to serve both of those purposes as well.

var request = require('request');
var logger = require('log4js');
logger.configure({
appenders: [
{type:'console'},
{type: 'file', filename: 'logs/application.log', catergory:'application'}
],
replaceConsole: true
});
exports.newMessage = function sparkMessage(sparkToken, text, to) {
request({
url: 'https://api.ciscospark.com/v1/messages',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + sparkToken
},
body:
JSON.stringify({'toPersonEmail': to,
'markdown': text})
}, function (error, response, body) {
if (error) {
console.log(error);
} else {
console.log(response.statusCode, body);
}
});
};
exports.getSpMeStatus = function sparkID(sparkToken, callback){
{
request({
url: 'https://api.ciscospark.com/v1/people/me',
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + sparkToken
},
}, function (error, response, body) {
if (error) {
console.log(error);
} else {
console.log(response.statusCode, body);
body = JSON.parse(body);
var status = body.status;
callback(status);
}
});
};
};
view raw sparkMethods.js hosted with ❤ by GitHub
Below is the main server file. I haven't done a lot of commenting inside my code but you will notice I have a bot monitoring service for some experimenting. I am just monitoring and posting my cloud containers CPU up-time to a bot account in Spark. Eventually I am thinking about sending these updates to another monitoring service which posts updates and alerts on bot issues. You will also notice that I have added the ability to translate presence from Spark into XMPP. It is simple and readable. Presence only updates if my Spark presence/activity changes.It is defaulted on startup to "inactive".

//DEBUG=* node server.js
var xmpp = require('simple-xmpp'),
http = require('http'),
fs = require('fs'),
path = require('path'),
logger = require('log4js'),
sparkMessage = require('./myutils/sparkMethods'),
os = require('os');
//Console logging to file
logger.configure({
appenders: [
{type:'console'},
{type: 'file', filename: 'logs/application.log', catergory:'application'}
],
replaceConsole: true
});
//Spark and bot tokens
var sparkToken = "<your Spark Token";
var to = '<Your email>';
var botToken = '<optional Bot token';
//Jabber Text messages
var jabberText = "Thank you for your message but I have moved to Cisco Spark "+
"as my primary contact application.\n"+
"I have automatically moved our conversation to Cisco Spark. "+
"If you have never used Spark before you should see an email invitation shortly. "+
"Also make sure to check out https://developer.ciscospark.com/ & https://www.ciscospark.com/ ."+
"To download Spark https://www.ciscospark.com/downloads.html. "+
"Oh and BTW don't forget my blog http://voipnorm.blogspot.com.\n"+
"See you in Spark...\n";
var jabberActiveText = "Thank you for your message but I have moved to Cisco Spark "+
"as my primary contact application.\n"+
"I have automatically moved our conversation to Cisco Spark and I am currently active which is the same as saying I am online. "+
"If you have never used Spark before you should see an email invitation shortly. "+
"Also make sure to check out https://developer.ciscospark.com/ & https://www.ciscospark.com/ ."+
"To download Spark https://www.ciscospark.com/downloads.html. "+
"Oh and BTW don't forget my blog http://voipnorm.blogspot.com.\n"+
"See you in Spark...\n";
var jabberDNDText = "********Do Not Disturb Alert***********\n"+
"Thank you for your message but I have moved to Cisco Spark "+
"as my primary contact application.\n"+
"I have automatically moved our conversation to Cisco Spark and I am in do not disturb right now so expect a delay. "+
"If you have never used Spark before you should see an email invitation shortly. "+
"Also make sure to check out https://developer.ciscospark.com/ & https://www.ciscospark.com/ ."+
"To download Spark https://www.ciscospark.com/downloads.html. "+
"Oh and BTW don't forget my blog http://voipnorm.blogspot.com.\n"+
"See you in Spark...\n";
var jabberOooText = "********Out of Office Alert***********\n"+
"Thank you for your message but I have moved to Cisco Spark "+
"as my primary contact application.\n"+
"I am currently out of the office so my responses may be delayed.\n"+
"I have automatically moved our conversation to Cisco Spark. "+
"If you have never used Spark before you should see an email invitation shortly. "+
"Also make sure to check out https://developer.ciscospark.com/ & https://www.ciscospark.com/ ."+
"To download Spark https://www.ciscospark.com/downloads.html. "+
"Oh and BTW don't forget my blog http://voipnorm.blogspot.com.\n"+
"See you in Spark...\n"+
"********Out of Office Alert***********";
//Default Spark Status
var currentStatus = 'inactive';
console.log(Date()+">>>>>>>NodeJS XMPP Jabber assistant bot...");
xmpp.on('online', function(data) {
console.log(Date()+'Connected with JID: ' + data.jid.user);
});
//Spark to Jabber Presence mapping
function presenceUpdate(){
setInterval(function(){
sparkMessage.getSpMeStatus(sparkToken, function(status){
if(currentStatus=== status){
return console.log('No Change to status: ' +currentStatus);
}else{
if(status === 'ooo'){
xmpp.setPresence('away', 'Out of Office, get me on Spark https://www.ciscospark.com/');
}else if(status === 'active'){
xmpp.setPresence('online', 'I am active on Spark https://www.ciscospark.com/');
}else if(status === 'inactive'){
xmpp.setPresence('away', 'Always available on Spark https://www.ciscospark.com/');
}else if(status === 'dnd'){
xmpp.setPresence('dnd', 'Ping me on Spark when active again https://www.ciscospark.com/');
}else{
xmpp.setPresence('away', 'Always available on Spark https://www.ciscospark.com/');
}
}
})
},60000);
}
presenceUpdate();
//service to monitor bot uptime
function postCPUCheck(){
setInterval(function(){
var cpuText = 'This is an uptime check for Jabber Assistant: '+os.uptime();
sparkMessage.newMessage(botToken,cpuText,to)
},8640000)
}
postCPUCheck();
//Return messages when some sends you a XMPP message
xmpp.on('chat', function(from, message) {
sparkMessage.getSpMeStatus(sparkToken, function(status){
if(status === 'ooo'){
var sparkText = "**Out of Office Alert**<br>"+
"Thank you for switching to **Cisco Spark** and taking part in the use of the Spark APIs. " +
"If this is your first time using Cisco Spark make sure to let me know and I will run you though the best features.<br>"+
"Just to make things easy here is your original message from Jabber: <br>"+
"**"+from+ "**: "+message+"<br>"+
"**Out of Office Alert**";
console.log('%s says %s', from, message);
xmpp.send(from,jabberOooText );
sparkMessage.newMessage(sparkToken,sparkText,from);
}else if(status === 'active'){
var sparkText = "Thank you for switching to **Cisco Spark** and taking part in the use of the Spark APIs. " +
"If this is your first time using Cisco Spark make sure to let me know and I will run you though the best features.</br>"+
"Just to make things easy here is your original message from Jabber: </br>"+
"**"+from+ "**: "+message;
console.log('%s says %s', from, message);
xmpp.send(from,jabberActiveText);
sparkMessage.newMessage(sparkToken,sparkText,from);
}else if(status === 'dnd'){
var sparkText = "**Do Not Disturb Alert**<br>"+
"Thank you for switching to **Cisco Spark** and taking part in the use of the Spark APIs. " +
"I am currentlu in **do not disturb** mode so it might take me a little while ot get back to you."+
"If this is your first time using Cisco Spark make sure to let me know and I will run you though the best features.</br>"+
"Just to make things easy here is your original message from Jabber: </br>"+
"**"+from+ "**: "+message;
console.log('%s says %s', from, message);
xmpp.send(from,jabberDNDText );
sparkMessage.newMessage(sparkToken,sparkText,from);
}else{
var sparkText = "Thank you for switching to **Cisco Spark** and taking part in the use of the Spark APIs. " +
"If this is your first time using Cisco Spark make sure to let me know and I will run you though the best features.</br>"+
"Just to make things easy here is your original message from Jabber: </br>"+
"**"+from+ "**: "+message;
console.log('%s says %s', from, message);
xmpp.send(from,jabberText );
sparkMessage.newMessage(sparkToken,sparkText,from);
};
});
});
xmpp.on('error', function(err) {
console.error(err);
console.error(err.stack);
});
//Handles lost connectiosn with XMPP service
xmpp.on('close', function() {
console.log('connection has been closed!');
sparkMessage.newMessage(botToken,'XMPP Connection Closed',to);
connect();
console.log('connection reopened');
sparkMessage.newMessage(botToken,'XMPP Connection Reopened',to);
});
function connect(){
xmpp.connect({
jid : '<your JID/email>',
password : '<your Passwrd>',
host : 'c2s.cisco.com.webexconnect.com',
port : 5222,
prefered: 'PLAIN',
reconnect: false
});
}
connect();
view raw xmppSparkBot.js hosted with ❤ by GitHub
This is a simple and fun personal bot to experiment with. Presence is new inside the Spark API and if your building a personal Spark bot the easiest way to get access is through the "get my own details". The "status" label gives your current presence.

Have fun coding!
VoIPNorm