borisjoffe / GVoice DND

// ==UserScript==
// @name         GVoice DND
// @namespace    http://boris.joff3.com
// @version      1.0
// @description  Show do not disturb button on main Google Voice page
// @match        https://*.google.com/voice*
// @copyright    2012, 2013, 2015 Boris Joffe
// @grant        GM_xmlhttpRequest
// ==/UserScript==

/*
    Copyright (C) 2012, 2013, 2015 Boris Joffe

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// Global Strings
const ENABLE_DND = "Enable DND";
const TOGGLE_TO_ENABLED = "Success! - Enabled Do Not Disturb";

// Global URLs
//const ENABLE_DND_URL = 'https://www.google.com/voice/settings/editGeneralSettings/';
const ENABLE_DND_URL = 'https://www.google.com/voice/b/0/service/post'; // new URL

// Search and Regexp strings
//const FIND_IF_DND_DISABLED_1 = '<span id="gc-dnd-static" style="display: none;">"Do Not Disturb" is enabled.';		// For DND with no end
//const FIND_IF_DND_DISABLED_2 = '<span id="gc-dnd-until" style="display: none;">"Do Not Disturb" is enabled';	// For DND until

// Other Global const
const TOGGLE_TIMEOUT = 300;
const DEFAULT_DND_MINUTES = 20;
const OPACITY_PERCENT = 0.65;

// Colors
//const FATALERR_COLOR = '#f00';
const ERR_COLOR = '#f31';
const WARN_COLOR = '#ffaa00';
const INFO_COLOR = '#999';
const BUTTON_BG_COLOR = '#f0f0ff';

// Globar DOM vars
var dndDiv;
var debugDiv;
var dndEnable, dndExp;

// URL Params
var _rnr_se;

// Global boolean vars
var DBG = false;
var ERR_VAL = -1;

window.addEventListener('load',
function() {

	main();

	function main() {
		if (DBG) addDebugPane();

		// Initialization
		//dbgInfol(document.getElementsByName('_rnr_se')[0].value);
		_rnr_se = name0('_rnr_se').value;
		dbgInfol('_rnr_se: ' + _rnr_se);

		makeDNDdiv(isDNDenabled());

		document.body.appendChild(dndDiv);
		if (DBG && debugDiv != null) document.body.appendChild(debugDiv);
	}

	// Return true if DND is enabled; otherwise return false
	// TODO - fix console error below as a result of elIDstyle
	// Error: uncaught exception: [Exception... "Operation is not supported"  code: "9" nsresult: "0x80530009 (NS_ERROR_DOM_NOT_SUPPORTED_ERR)"  location: "resource://greasemonkey/runScript.js Line: 118"]
	function isDNDenabled() {
		const FIND_STATIC_DND = 'gc-dnd-display';
		var s = elIDstyle(FIND_STATIC_DND);

		if (s == null || s == ERR_VAL) {
		  dbgErrl('isDNDenabled/elIDstyle - s is: ' + s);
		  return ERR_VAL;
		}
		s = s.display;
		if (s == ERR_VAL) {
		  dbgErrl('isDNDenabled/elIDstyle returned err');
		  return ERR_VAL;
		}
		//dbgInfol('display: ' + s);
		return s.indexOf('none') != -1;
	}


	// Create DND Div
	function makeDNDdiv(isEnabled) {
		if (DBG && isEnabled) dbgInfol("dnd enabled but making div anyway");

		dndDiv = document.createElement('div');
		dndDiv.id = "dndDiv";
		dndDiv.style.position = 'fixed';
		dndDiv.style.top = "23%";
		dndDiv.style.right = "10%";
		//dndDiv.style.border = "1px solid blue";
		dndDiv.style.padding = "7px";
		dndDiv.style.backgroundColor = "#002"; //"#f0f0ff";
		dndDiv.style.opacity = OPACITY_PERCENT;
		dndDiv.style.zIndex="100";	// put it in front of other elements
		dndDiv.style.minWidth = "200px";

		dndEnable = document.createElement('button');
		dndEnable.style.padding = '1px';
		dndEnable.style.border = '1px solid blue';
		dndEnable.style.margin = '5px';
		dndEnable.style.backgroundColor = BUTTON_BG_COLOR;
		dndEnable.textContent = ENABLE_DND;

		// Expiration Time Field - in unixtime
		dndExp = document.createElement('input');
		dndExp.id = "dndExp";
		dndExp.type = "text";
		dndExp.size = 13;
		dndExp.style.display = "none";
		//dndExp.style.visible = "none";
		if (DBG) dndExp.style.display = "inline";

		// How long to set DND for
		var dndExpDate = document.createElement('input');
		dndExpDate.id = "dndExpDate";
		dndExpDate.type = "text";
		dndExpDate.size = 4;

		//var expValue = new Date( dndExp.value );
		dndExpDate.value = DEFAULT_DND_MINUTES;

		// Units of time to set DND for
		var dndExpUnit = document.createElement('select');
		dndExpUnit.id = "dndExpUnit";
		var minOpt = document.createElement('option');
		minOpt.value = minOpt.text = 'Min';
		var hrOpt = document.createElement('option');
		hrOpt.value = hrOpt.text = 'Hr';
		dndExpUnit.add(minOpt);
		dndExpUnit.add(hrOpt);
		dndExpUnit.selectedIndex = 0;
		//dndExpUnit.size = 2;

		var dndFutureDate = document.createElement('span');
		dndFutureDate.id = "dndFutureDate";
		dndFutureDate.innerHTML = "";
		dndFutureDate.style.color = "#fc0";
		dndFutureDate.style.fontSize = 8;
		//dndFutureDate.style.margin = "2px";
		//dndFutureDate.style.padding = "2px";

		dndDiv.appendChild(dndExpDate);
		dndDiv.appendChild(dndExpUnit);
		dndDiv.appendChild(dndEnable);
		if (DBG) dndDiv.appendChild( document.createElement('br') );
		dndDiv.appendChild(dndExp);
		dndDiv.appendChild( document.createElement('br') );
		dndDiv.appendChild(dndFutureDate);


	   // Create event listener toggle
		dndEnable.addEventListener('click', enableDND, true);
		dndExpDate.addEventListener('change', expChange, true);
		dndExpDate.addEventListener('click', expChange, true);
		dndExpUnit.addEventListener('change', expChange, true);

		// When Do Not Distrub expiration date changes
		// Update seconds
		function expChange() {
		   var addMin = 0;
		   var addHr = 0;
		   var futureDate = new Date();

		   if ( dndExpUnit.selectedIndex == 0 ) {  // minutes selected
			   addMin = dndExpDate.value;
			   addMin = addMin * 60 * 1000;    // convert to UNIX ms
			   futureDate.setTime( futureDate.getTime() + addMin );
		   } else if ( dndExpUnit.selectedIndex == 1 ) { // hours selected
			   addHr = dndExpDate.value;
			   addHr = addHr * 60 * 60 * 1000;  // convert to UNIX ms
			   futureDate.setTime( futureDate.getTime() + addHr );
		   } else {
			   dbgErrl('expChange evt: could not get selectedIndex');
			   return ERR_VAL;
		   }

		   dndExp.value = futureDate.getTime();    // update unix time
		   dbgInfol('futureDate: ' + futureDate);
		   dndFutureDate.innerHTML = futureDate;

		}

		// send AJAX POST request to enable DND for unspecified amt of time
		function enableDND() {
			dbgInfol('enabling');
			var actualReq1, actualReq2;
			//var oldReq = "doNotDisturb=1&_rnr_se=";

			actualReq1 = 'sid=3&mid=5&req=%5Bnull%2C%5Bnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cfalse%2C';
			actualReq2 = '%5D%5D&_rnr_se=';

			expChange();
			var myFullReq = actualReq1 + dndExp.value + actualReq2 + encodeURIComponent(_rnr_se);

			GM_xmlhttpRequest({
			  method: "POST",
			  url: ENABLE_DND_URL,
			  data: myFullReq,
			  headers: {
				"Content-Type": "application/x-www-form-urlencoded"
			  },
			  onload: function(/*response*/) {
				//dndEnable.innerHTML = TOGGLE_TO_ENABLED;
				dndFutureDate.innerHTML = TOGGLE_TO_ENABLED;
				//dbgInfol( 'Sending request:\n\n' +  myFullReq);
				setTimeout(window.location.reload(true), TOGGLE_TIMEOUT);  // is this useless like the dev console says??? - gv does a 2nd get request to update the page
			  },
			  onerror: function(response) {
				  if (DBG) alert('Err: \n\n' + response);
			  }
			});
		}
	}

	// Helper Functions
	function el(elStr) {
		var x = document.getElementById(elStr);    // get el by ID
		if (x == null) {
		   dbgErrl("el: no el by id: " + elStr);
		   return ERR_VAL;
		} else
		   return x;
	}
	/*function elStyle(el) {
	   var x = window.getComputedStyle(el, null);	// get computed style of el
	   if (x == null) {
		   dbgErrl('elStyle: no el style for: ' + el);
		   return ERR_VAL;
	   } else
		   return x;
	}*/

	function elIDstyle(elStr) {
	   var x = el(elStr);
	   if (x==ERR_VAL) {
		   dbgErrl('elIDStyle/el - el by ID returned error');
		   return ERR_VAL;
	   }
	   x = window.getComputedStyle( x, null ); 	// get computed style from el ID str

	   //dbgInfol("computed style:\n"+x.content);

	   if (x == null) {
		   dbgErrl('elIDStyle: could not get el style from elbyId: ' + elStr);
		   return ERR_VAL;
	   } else
		   return x;
	}

	function name0(elStr) {
	   var x = document.getElementsByName(elStr); // get first element of a certain name
	   if (x == null) {
		   dbgErrl("name0: no el by name: " + elStr);
		   return ERR_VAL;
	   } else if (x[0]==null) {
		   dbgErrl('name0: el exists but el[0] doesnt');
		   return ERR_VAL;
	   } else if (x[0].value==null) {
		   dbgWarnl('name0: el[0].value is null');
		   return x[0];
	   } else
		   return x[0];
	}

	// ========== DEBUGGING SECTION =========
	// Adds a fixed div at the top right with debug info
	function addDebugPane() {
		debugDiv = document.createElement('div');
		debugDiv.id = "debugDiv";

		//Some CSS stuff
		debugDiv.style.position = "fixed";
		debugDiv.style.top = "38%";
		debugDiv.style.right = "5%";
		debugDiv.style.padding = "5px";
		debugDiv.style.border = "2px solid red";
		debugDiv.style.backgroundColor = "white";
		//debugDiv.style.minWidth = "20%";
		debugDiv.style.zIndex="100";	// put it in front of other elements

		debugDiv.innerHTML = "<b><u>Debug Info</u></b><br>";
	}

	// Add info to debug div
	function dbgAdd(htmlText) {
		if (DBG && debugDiv != null)
			debugDiv.innerHTML += htmlText;
		else if (DBG) {
			var tmpDiv = document.createElement('div');
			tmpDiv.innerHTML = htmlText;
			alert('DBG info:\n\t' + tmpDiv.textContent + ' \n\n\n(Note: debugDiv is null)');
		}
	}
	function dbgAddl(htmlText) { dbgAdd(htmlText + "<br>"); }
	//function dbgFatal(htmlText) { dbgAdd('<b style="color:' + FATALERR_COLOR + ';">Fatal Err:</b> ' + htmlText);}
	//function dbgFatall(htmlText) { dbgAddl('<b style="color:' + FATALERR_COLOR + ';">Fatal Err:</b> ' + htmlText);}
	//function dbgErr(htmlText) { dbgAdd('<b style="color:' + ERR_COLOR + ';">Err:</b> ' + htmlText); }
	function dbgErrl(htmlText) { dbgAddl('<b style="color:' + ERR_COLOR + ';">Err:</b> ' + htmlText); }
	//function dbgWarn(htmlText) { dbgAdd('<i style="color:' + WARN_COLOR + ';">Warning:</i> ' + htmlText); }
	function dbgWarnl(htmlText) { dbgAddl('<i style="color:' + WARN_COLOR + ';">Warning:</i> ' + htmlText); }
	//function dbgInfo(htmlText) { dbgAdd('<i style="color:' + INFO_COLOR + ';">Info:</i> ' + htmlText); }
	function dbgInfol(htmlText) { dbgAddl('<i style="color:' + INFO_COLOR + ';">Info:</i> ' + htmlText); }

}, true);