<commit_before>// Forked from https://github.com/kevinmhickey/adt-pulse

var tough = require('tough-cookie');
var request = require('request');
var q = require('q');
var cheerio = require('cheerio');
var _ = require('lodash');

//Cookie jar
var j;
var ua =  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36';
var sat = '';
var lastsynckey = '';
var deviceUpdateCB = function () {};
var zoneUpdateCB = function () {};
var statusUpdateCB = function () {};

pulse = function(username, password) {

	this.authenticated = false;
	this.isAuthenticating = false;
	this.clients = [];

	this.configure({
		username: username,
		password: password
	});

	/* heartbeat */
	var pulseInterval = setInterval(this.sync.bind(this),5000);
};

module.exports = pulse;

(function() {

	this.config = {
		baseUrl: 'https://portal.adtpulse.com',
		prefix: '/myhome/13.0.0-153', // you don't need to change this every time. Addon automatically grabs the latest one on the first call.
		initialURI: '/',
		signinURI: '/access/signin.jsp',
		authURI: '/access/signin.jsp?e=n&e=n&&partner=adt',
		sensorURI: '/ajax/homeViewDevAjax.jsp',
		sensorOrbURI: '/ajax/orb.jsp',
		summaryURI: '/summary/summary.jsp',
		statusChangeURI: '/quickcontrol/serv/ChangeVariableServ',
		armURI: '/quickcontrol/serv/RunRRACommand',
		disarmURI: '/quickcontrol/armDisarm.jsp?href=rest/adt/ui/client/security/setArmState',
		otherStatusURI: '/ajax/currentStates.jsp',
		syncURI: '/Ajax/SyncCheckServ',
		logoutURI: '/access/signout.jsp',

		orbUrl: 'https://portal.adtpulse.com/myhome/9.7.0-31/ajax/orb.jsp' // not used
	};

	this.configure = function(options) {
		for(o in options){
			this.config[o] = options[o];
		}
	};

	this.login = function () {

		var deferred = q.defer();
		var that = this;

		if(this.authenticated){
			deferred.resolve()
		} else {
			console.log((new Date()).toLocaleString()+' Pulse: Login called Authenticating');

			j = request.jar();

			that.isAuthenticating = true;
			request(
				{
					url: this.config.baseUrl+this.config.initialURI, // call with no prefix to grab the prefix
					jar: j,
					headers: {
						'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
						'User-Agent': ua
					},
				},
				function(e, hResp, b) {
					// expecting /myhome/VERSION/access/signin.jsp
					if (hResp==null){
							console.log((new Date()).toLocaleString() + ' Pulse: Authentication bad response error:'+JSON.stringify(e));
							that.authenticated =false;
							that.isAuthenticating = false;
							deferred.reject();
							return deferred.promise;
					}
					console.log((new Date()).toLocaleString() + ' Pulse: Authentication Received Pathname: '+hResp.request.uri.pathname);

					var uriPart = hResp.request.uri.pathname.match(/\/myhome\/(.+?)\/access/)[1];
					console.log((new Date()).toLocaleString() + ' Pulse: Authentication Page Version: '+uriPart);
					that.config.prefix= '/myhome/'+uriPart;
					console.log((new Date()).toLocaleString() + ' Pulse: Authentication New URL Prefix '+ that.config.prefix);
					console.log((new Date()).toLocaleString() + ' Pulse: Authentication  Calling '+ that.config.baseUrl+that.config.prefix+that.config.authURI);
					request.post(that.config.baseUrl+that.config.prefix+that.config.authURI,
						{
							followAllRedirects: true,
							jar: j,
							headers: {
								'Host': 'portal.adtpulse.com',
								'User-Agent': ua
							},
							form:{
								username: that.config.username,
								password: <PASSWORD>
							}
						},
						function(err, httpResponse, body){
							that.isAuthenticating = false;
							if(err || httpResponse.request.path !== that.config.prefix+that.config.summaryURI){
								that.authenticated = false;
								console.log((new Date()).toLocaleString() + ' Pulse: Authentication Failed');
								console.log((new Date()).toLocaleString() + ' Pulse: httpResponse:' + httpResponse);
								deferred.reject();
							} else {
								that.authenticated = true;
								console.log((new Date()).toLocaleString() + ' Pulse: Authentication Success');
								deferred.resolve();
								that.updateAll.call(that);
							}
						}
					);


				}
			);
		}



		return deferred.promise
	},

	this.logout = function () {

		var that = this;

		console.log((new Date()).toLocaleString() + ' Pulse: Logout');

		request(
			{
				url: this.config.baseUrl+this.config.prefix+this.config.logoutURI,
				jar: j,
				headers: {
					'User-Agent': ua
				}
			},
			function () {
				that.authenticated = false;
			}
		)
	},

	this.updateAll = function () {
		var that = this;
		console.log((new Date()).toLocaleString() + ' Pulse: updateAll');

		this.getAlarmStatus().then(function(){
			that.getDeviceStatus();
			that.getZoneStatusOrb();
		});
	}

	this.getZoneStatus = function() {
		console.log((new Date()).toLocaleString() + ' Pulse.getZoneStatus: Getting Zone Statuses');
		var deferred = q.defer();
		request(
			{
				url: this.config.baseUrl+this.config.prefix+this.config.sensorURI,
				jar: j,
				headers: {
					'User-Agent': ua
				},
			},
			function(err, httpResponse, body) {
				if(err){
					console.log((new Date().toLocaleString()) + ' Pulse.getZoneStatus: Zone JSON Failed');
				} else {
					try {
						var json = JSON.parse(body.trim());
						if (json != null){
							console.log((new Date().toLocaleString()) + ' DEBUG: Raw JSON:' + json.stringify());
							json.items.forEach(function(obj){
									o = obj;
									delete o.deprecatedAction;
									o.status = obj.state.icon;
									o.statusTxt = obj.state.statusTxt;
									o.activityTs = obj.state.activityTs;
									delete o.state;
								zoneUpdateCB(o);
							})
						} else {
							console.log((new Date().toLocaleString())+ ' Pulse: No Zone JSON');
						}
					} catch(e) {
					   console.log((new Date().toLocaleString()) + ' Pulse: Invalid Zone JSON'+ e.stack);
					}
				}

			}
		);

		return deferred.promise;
	},

	this.getZoneStatusOrb = function() {
		console.log((new Date()).toLocaleString() + ' Pulse.getZoneStatus(via Orb): Getting Zone Statuses');
		var deferred = q.defer();
		request(
			{
				url: this.config.baseUrl+this.config.prefix+this.config.sensorOrbURI,
				jar: j,
				headers: {
					'User-Agent': ua,
					'Referer': this.config.baseUrl+this.config.prefix+this.summaryURI
				},
			},
			function(err, httpResponse, body) {
				if(err){
					console.log((new Date().toLocaleString()) + ' Pulse.getZoneStatus (via Orb): Zone JSON Failed');
				} else {
						// Load response from call to Orb and parse html
						const $ = cheerio.load(body);
						const sensors = $('#orbSensorsList table tr.p_listRow').toArray();
						// Map values of table to variables
						const output = _.map(sensors,(sensor) => {
							const theSensor = cheerio.load(sensor);
							const theName = theSensor('a.p_deviceNameText').html();
							const theZone = theSensor('span.p_grayNormalText').html();
							const theState = theSensor('span.devStatIcon canvas').attr('icon');

							const theZoneNumber = (theZone) ? theZone.replace(/(Zone&#xA0;)([0-9]{1,2})/, '$2') : 0;

							let theTag;

							if (theName && theState !== 'devStatUnknown') {
								if (theName.includes('Door') || theName.includes('Window')) {
								  theTag = 'sensor,doorWindow';
								} else if (theName.includes('Glass')) {
								  theTag = 'sensor,glass';
								} else if (theName.includes('Motion')) {
								  theTag = 'sensor,motion';
								} else if (theName.includes('Gas')) {
								  theTag = 'sensor,co';
								} else if (theName.includes('Smoke') || theName.includes('Heat')) {
								  theTag = 'sensor,fire';
								}
							  }
							/**
							 * Expected output.
							 *
							 * id:    sensor-[integer]
							 * name:  device name
							 * tags:  sensor,[doorWindow,motion,glass,co,fire]
							 * timestamp: timestamp of last activity
							 * state: devStatOK (device okay)
							 *        devStatOpen (door/window opened)
							 *        devStatMotion (detected motion)
							 *        devStatTamper (glass broken or device tamper)
							 *        devStatAlarm (detected CO/Smoke)
							 *        devStatUnknown (device offline)
							 */
							return {
								id: `sensor-${theZoneNumber}`,
								name: theName || 'Unknown Sensor',
								tags: theTag || 'sensor',
								timestamp: new Date.now();
								state: theState || 'devStatUnknown',
							  };

							});

							console.log((new Date().toLocaleString()) + 'ADT Pulse: Get zone status (via orb) success.');
							output.forEach(function(obj){
								s = obj;<commit_msg>fix comma<commit_after>// Forked from https://github.com/kevinmhickey/adt-pulse

var tough = require('tough-cookie');
var request = require('request');
var q = require('q');
var cheerio = require('cheerio');
var _ = require('lodash');

//Cookie jar
var j;
var ua =  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36';
var sat = '';
var lastsynckey = '';
var deviceUpdateCB = function () {};
var zoneUpdateCB = function () {};
var statusUpdateCB = function () {};

pulse = function(username, password) {

	this.authenticated = false;
	this.isAuthenticating = false;
	this.clients = [];

	this.configure({
		username: username,
		password: password
	});

	/* heartbeat */
	var pulseInterval = setInterval(this.sync.bind(this),5000);
};

module.exports = pulse;

(function() {

	this.config = {
		baseUrl: 'https://portal.adtpulse.com',
		prefix: '/myhome/13.0.0-153', // you don't need to change this every time. Addon automatically grabs the latest one on the first call.
		initialURI: '/',
		signinURI: '/access/signin.jsp',
		authURI: '/access/signin.jsp?e=n&e=n&&partner=adt',
		sensorURI: '/ajax/homeViewDevAjax.jsp',
		sensorOrbURI: '/ajax/orb.jsp',
		summaryURI: '/summary/summary.jsp',
		statusChangeURI: '/quickcontrol/serv/ChangeVariableServ',
		armURI: '/quickcontrol/serv/RunRRACommand',
		disarmURI: '/quickcontrol/armDisarm.jsp?href=rest/adt/ui/client/security/setArmState',
		otherStatusURI: '/ajax/currentStates.jsp',
		syncURI: '/Ajax/SyncCheckServ',
		logoutURI: '/access/signout.jsp',

		orbUrl: 'https://portal.adtpulse.com/myhome/9.7.0-31/ajax/orb.jsp' // not used
	};

	this.configure = function(options) {
		for(o in options){
			this.config[o] = options[o];
		}
	};

	this.login = function () {

		var deferred = q.defer();
		var that = this;

		if(this.authenticated){
			deferred.resolve()
		} else {
			console.log((new Date()).toLocaleString()+' Pulse: Login called Authenticating');

			j = request.jar();

			that.isAuthenticating = true;
			request(
				{
					url: this.config.baseUrl+this.config.initialURI, // call with no prefix to grab the prefix
					jar: j,
					headers: {
						'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
						'User-Agent': ua
					},
				},
				function(e, hResp, b) {
					// expecting /myhome/VERSION/access/signin.jsp
					if (hResp==null){
							console.log((new Date()).toLocaleString() + ' Pulse: Authentication bad response error:'+JSON.stringify(e));
							that.authenticated =false;
							that.isAuthenticating = false;
							deferred.reject();
							return deferred.promise;
					}
					console.log((new Date()).toLocaleString() + ' Pulse: Authentication Received Pathname: '+hResp.request.uri.pathname);

					var uriPart = hResp.request.uri.pathname.match(/\/myhome\/(.+?)\/access/)[1];
					console.log((new Date()).toLocaleString() + ' Pulse: Authentication Page Version: '+uriPart);
					that.config.prefix= '/myhome/'+uriPart;
					console.log((new Date()).toLocaleString() + ' Pulse: Authentication New URL Prefix '+ that.config.prefix);
					console.log((new Date()).toLocaleString() + ' Pulse: Authentication  Calling '+ that.config.baseUrl+that.config.prefix+that.config.authURI);
					request.post(that.config.baseUrl+that.config.prefix+that.config.authURI,
						{
							followAllRedirects: true,
							jar: j,
							headers: {
								'Host': 'portal.adtpulse.com',
								'User-Agent': ua
							},
							form:{
								username: that.config.username,
								password: <PASSWORD>
							}
						},
						function(err, httpResponse, body){
							that.isAuthenticating = false;
							if(err || httpResponse.request.path !== that.config.prefix+that.config.summaryURI){
								that.authenticated = false;
								console.log((new Date()).toLocaleString() + ' Pulse: Authentication Failed');
								console.log((new Date()).toLocaleString() + ' Pulse: httpResponse:' + httpResponse);
								deferred.reject();
							} else {
								that.authenticated = true;
								console.log((new Date()).toLocaleString() + ' Pulse: Authentication Success');
								deferred.resolve();
								that.updateAll.call(that);
							}
						}
					);


				}
			);
		}



		return deferred.promise
	},

	this.logout = function () {

		var that = this;

		console.log((new Date()).toLocaleString() + ' Pulse: Logout');

		request(
			{
				url: this.config.baseUrl+this.config.prefix+this.config.logoutURI,
				jar: j,
				headers: {
					'User-Agent': ua
				}
			},
			function () {
				that.authenticated = false;
			}
		)
	},

	this.updateAll = function () {
		var that = this;
		console.log((new Date()).toLocaleString() + ' Pulse: updateAll');

		this.getAlarmStatus().then(function(){
			that.getDeviceStatus();
			that.getZoneStatusOrb();
		});
	}

	this.getZoneStatus = function() {
		console.log((new Date()).toLocaleString() + ' Pulse.getZoneStatus: Getting Zone Statuses');
		var deferred = q.defer();
		request(
			{
				url: this.config.baseUrl+this.config.prefix+this.config.sensorURI,
				jar: j,
				headers: {
					'User-Agent': ua
				},
			},
			function(err, httpResponse, body) {
				if(err){
					console.log((new Date().toLocaleString()) + ' Pulse.getZoneStatus: Zone JSON Failed');
				} else {
					try {
						var json = JSON.parse(body.trim());
						if (json != null){
							console.log((new Date().toLocaleString()) + ' DEBUG: Raw JSON:' + json.stringify());
							json.items.forEach(function(obj){
									o = obj;
									delete o.deprecatedAction;
									o.status = obj.state.icon;
									o.statusTxt = obj.state.statusTxt;
									o.activityTs = obj.state.activityTs;
									delete o.state;
								zoneUpdateCB(o);
							})
						} else {
							console.log((new Date().toLocaleString())+ ' Pulse: No Zone JSON');
						}
					} catch(e) {
					   console.log((new Date().toLocaleString()) + ' Pulse: Invalid Zone JSON'+ e.stack);
					}
				}

			}
		);

		return deferred.promise;
	},

	this.getZoneStatusOrb = function() {
		console.log((new Date()).toLocaleString() + ' Pulse.getZoneStatus(via Orb): Getting Zone Statuses');
		var deferred = q.defer();
		request(
			{
				url: this.config.baseUrl+this.config.prefix+this.config.sensorOrbURI,
				jar: j,
				headers: {
					'User-Agent': ua,
					'Referer': this.config.baseUrl+this.config.prefix+this.summaryURI
				},
			},
			function(err, httpResponse, body) {
				if(err){
					console.log((new Date().toLocaleString()) + ' Pulse.getZoneStatus (via Orb): Zone JSON Failed');
				} else {
						// Load response from call to Orb and parse html
						const $ = cheerio.load(body);
						const sensors = $('#orbSensorsList table tr.p_listRow').toArray();
						// Map values of table to variables
						const output = _.map(sensors,(sensor) => {
							const theSensor = cheerio.load(sensor);
							const theName = theSensor('a.p_deviceNameText').html();
							const theZone = theSensor('span.p_grayNormalText').html();
							const theState = theSensor('span.devStatIcon canvas').attr('icon');

							const theZoneNumber = (theZone) ? theZone.replace(/(Zone&#xA0;)([0-9]{1,2})/, '$2') : 0;

							let theTag;

							if (theName && theState !== 'devStatUnknown') {
								if (theName.includes('Door') || theName.includes('Window')) {
								  theTag = 'sensor,doorWindow';
								} else if (theName.includes('Glass')) {
								  theTag = 'sensor,glass';
								} else if (theName.includes('Motion')) {
								  theTag = 'sensor,motion';
								} else if (theName.includes('Gas')) {
								  theTag = 'sensor,co';
								} else if (theName.includes('Smoke') || theName.includes('Heat')) {
								  theTag = 'sensor,fire';
								}
							  }
							/**
							 * Expected output.
							 *
							 * id:    sensor-[integer]
							 * name:  device name
							 * tags:  sensor,[doorWindow,motion,glass,co,fire]
							 * timestamp: timestamp of last activity
							 * state: devStatOK (device okay)
							 *        devStatOpen (door/window opened)
							 *        devStatMotion (detected motion)
							 *        devStatTamper (glass broken or device tamper)
							 *        devStatAlarm (detected CO/Smoke)
							 *        devStatUnknown (device offline)
							 */
							return {
								id: `sensor-${theZoneNumber}`,
								name: theName || 'Unknown Sensor',
								tags: theTag || 'sensor',
								timestamp: new Date.now(),
								state: theState || 'devStatUnknown',
							  };

							});

							console.log((new Date().toLocaleString()) + 'ADT Pulse: Get zone status (via orb) success.');
							output.forEach(function(obj){
								s = obj;
{'fraction_non_alphanumeric': 0.15848317196864914, 'fraction_numerical': 0.006224066390041493, 'mean_word_length': 2.7855584642233855, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5419567', 'n_tokens_mistral': 7433, 'n_tokens_neox': 5522, 'n_words': 1100}