dfxdfx.at / Geocaching TB auto-visit

// ==UserScript==
// @name         Geocaching TB auto-visit
// @namespace    http://tampermonkey.net/
// @version      3.2
// @description  Adds TB auto-visit functionality to geocaching.com
// @author       dfx
// @match        http*://*.geocaching.com/seek/log.asp*
// @match        http*://*.geocaching.com/play/geocache/*/log*
// @grant        none
// @require      http://code.jquery.com/jquery-3.2.1.min.js
// ==/UserScript==
/* jshint -W097 */
'use strict';


var maxLogLen = 4000;
var jq = jQuery.noConflict(true);



/*--- waitForKeyElements():  A utility function, for Greasemonkey scripts,
    that detects and handles AJAXed content.

    Usage example:

        waitForKeyElements (
            "div.comments"
            , commentCallbackFunction
        );

        //--- Page-specific function to do what we want when the node is found.
        function commentCallbackFunction (jNode) {
            jNode.text ("This comment changed by waitForKeyElements().");
        }

    IMPORTANT: This function requires your script to have loaded jQuery.
*/
function waitForKeyElements (
    selectorTxt,    /* Required: The jQuery selector string that
                        specifies the desired element(s).
                    */
    actionFunction, /* Required: The code to run when elements are
                        found. It is passed a jNode to the matched
                        element.
                    */
    bWaitOnce,      /* Optional: If false, will continue to scan for
                        new elements even after the first match is
                        found.
                    */
    iframeSelector  /* Optional: If set, identifies the iframe to
                        search.
                    */
) {
    var targetNodes, btargetsFound;

    if (typeof iframeSelector == "undefined")
        targetNodes     = jq(selectorTxt);
    else
        targetNodes     = jq(iframeSelector).contents ()
                                           .find (selectorTxt);

    if (targetNodes  &&  targetNodes.length > 0) {
        btargetsFound   = true;
        /*--- Found target node(s).  Go through each and act if they
            are new.
        */
        targetNodes.each ( function () {
            var jThis        = jq(this);
            var alreadyFound = jThis.data ('alreadyFound')  ||  false;

            if (!alreadyFound) {
                //--- Call the payload function.
                var cancelFound     = actionFunction (jThis);
                if (cancelFound)
                    btargetsFound   = false;
                else
                    jThis.data ('alreadyFound', true);
            }
        } );
    }
    else {
        btargetsFound   = false;
    }

    //--- Get the timer-control variable for this selector.
    var controlObj      = waitForKeyElements.controlObj  ||  {};
    var controlKey      = selectorTxt.replace (/[^\w]/g, "_");
    var timeControl     = controlObj [controlKey];

    //--- Now set or clear the timer as appropriate.
    if (btargetsFound  &&  bWaitOnce  &&  timeControl) {
        //--- The only condition where we need to clear the timer.
        clearInterval (timeControl);
        delete controlObj [controlKey];
    }
    else {
        //--- Set a timer, if needed.
        if ( ! timeControl) {
            timeControl = setInterval ( function () {
                    waitForKeyElements (    selectorTxt,
                                            actionFunction,
                                            bWaitOnce,
                                            iframeSelector
                                        );
                },
                300
            );
            controlObj [controlKey] = timeControl;
        }
    }
    waitForKeyElements.controlObj   = controlObj;
}



if (typeof GM_deleteValue == 'undefined') {

    var GM_addStyle = function(css) {
        var style = document.createElement('style');
        style.textContent = css;
        document.getElementsByTagName('head')[0].appendChild(style);
    };

    var GM_deleteValue = function(name) {
        localStorage.removeItem(name);
    };

    var GM_getValue = function(name, defaultValue) {
        var value = localStorage.getItem(name);
        if (!value)
            return defaultValue;
        var type = value[0];
        value = value.substring(1);
        switch (type) {
            case 'b':
                return value == 'true';
            case 'n':
                return Number(value);
            default:
                return value;
        }
    };

    var GM_log = function(message) {
        console.log(message);
    };

    var GM_openInTab = function(url) {
        return window.open(url, "_blank");
    };

    var GM_registerMenuCommand = function(name, funk) {
    //todo
    };

    var GM_setValue = function(name, value) {
        value = (typeof value)[0] + value;
        localStorage.setItem(name, value);
    };
}


//  Get currently signed-on geocaching.com profile.
var SignedInLink = document.evaluate(
		"//a[contains(@class, 'SignedInProfileLink')]",
		document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
		).singleNodeValue;
if (SignedInLink) {
	var SignedInAs = SignedInLink.firstChild.data;
	var NewRel = true;
} else {
    SignedInLink = jq('.li-user-info');
    if (SignedInLink) {
        // new log page
        SignedInLink = SignedInLink[0];
        for (var c = SignedInLink.firstChild; c; c = c.nextSibling) {
            if (c.nodeName != 'SPAN')
                continue;
            if (c.childNodes.length > 1)
                continue;
            SignedInAs = c.childNodes[0].data;
            break;
        }
    }
    else {
	    var e_LogIn = document.getElementById("ctl00_LoginUrl");
	    if (!e_LogIn) {
    		var e_LogIn = document.getElementById("Header1_urlLogin");
    	}
    	if (e_LogIn.firstChild.data != 'Log out') { return; }
    	SignedInAs = e_LogIn.parentNode.childNodes[1].firstChild.data;
    	NewRel = false;
    }
}

SignedInAs = SignedInAs.replace(/<\&amp;>/g, '&');
GM_log("signed in as " + SignedInAs);

var bShowInventory = false; // default

//  Get handle to page's form.
var e_LogTextArea = document.getElementById("ctl00_ContentBody_LogBookPanel1_uxLogInfo");
if (! e_LogTextArea) {
    // new log page
    e_LogTextArea = jq("#LogText")[0];
    if (!e_LogTextArea)
        return;
}
var e_LogBookPanel1_LogButton = document.getElementById("ctl00_ContentBody_LogBookPanel1_btnSubmitLog");
if (e_LogBookPanel1_LogButton) {
    // old log page
    //  Move submit button to the right.
    e_LogBookPanel1_LogButton.parentNode.style.textAlign = 'right';

    //  Create duplicate Submit button.
    var LogButtonTop = e_LogBookPanel1_LogButton.cloneNode(false);
    //var tmpDD = document.getElementById("ctl00_ContentBody_LogBookPanel1_uxDateFormatHint").parentNode.parentNode;
    var tmpDD = document.getElementById("uxDateVisited").parentNode;
    tmpDD.appendChild(LogButtonTop);
}
else {
    //e_LogBookPanel1_LogButton = jq('.btn-submit')[0];
}

//  Add column to trackable table, if present.
var arTrackers = [ ];  //  Needs to be a global variable, so it can be used in functions.
var e_tblTravelBugs = document.getElementById("tblTravelBugs");
var cp = 1;
if (e_tblTravelBugs) {
	var e_headTravelBugs = e_tblTravelBugs.childNodes[1].childNodes[1];
	var avHead = document.createElement('th');
	avHead.style.width = '100px';
	avHead.appendChild(document.createTextNode('Auto-Visit'));
	insertAfter(avHead, e_headTravelBugs.cells[cp]);

	var e_bodyTravelBugs = e_tblTravelBugs.childNodes[3];
	for (var r = 0; r < e_bodyTravelBugs.rows.length; r++) {
			if (r == e_bodyTravelBugs.rows.length - 1) {
				e_bodyTravelBugs.rows[r].cells[0].setAttribute('colspan', 4);
			} else {
				var avTracker = e_bodyTravelBugs.rows[r].cells[0].firstChild.firstChild.data;
				var avDrpDwn = e_bodyTravelBugs.rows[r].cells[2].childNodes[1].id;
				arTrackers.push(avTracker);
				var avCol = document.createElement('td');
				insertAfter(avCol, e_bodyTravelBugs.rows[r].cells[cp]);
				var avChkBox = document.createElement('input');
				avChkBox.type = 'Checkbox';
				avChkBox.id = 'chkbox_' + avTracker;
				avChkBox.setAttribute('tracker', avTracker);
				avChkBox.setAttribute('row', r);
    			avChkBox.setAttribute('drpdwnctl', avDrpDwn);
				avChkBox.addEventListener("click", fAutoVisitChanged, true);
				var avCbLbl = document.createElement('label');
				avCbLbl.setAttribute('for', 'chkbox_' + avTracker);
				avCbLbl.appendChild(document.createTextNode('Auto-Visit'));
				avCol.appendChild(avChkBox);
				avCol.appendChild(avCbLbl);
			}
	}
	fUpdateAutoVisit(false);
}
else {
    // new log page
    GM_log("new log page!");
    waitForKeyElements('div.gameplay-list-item', new_page_trackables);
}

//  Update checkboxs whenever window gets focus, so changes in other tabs are recognized.
window.document.addEventListener("focus", fWinFocus, true);




	var vsdt = document.getElementById("ctl00_ContentBody_LogBookPanel1_valLogInfoMaxLength");
	if (vsdt) {

		//  Create span to hold new controls.
		var spanCtrls = document.createElement("span");
		spanCtrls.id = 'spanCtrls';
		spanCtrls.style.whiteSpace = 'nowrap';
		spanCtrls.style.fontSize = '20px';
//		vsdt.appendChild(spanCtrls);
		insertAheadOf(spanCtrls, vsdt);

		//  Create span to hold Character counter control.
		var spanCounter = document.createElement("span");
		spanCounter.id = 'spanCounter';

		//  Character counter and add to span.
		//var spanCharsLeft = document.createElement("span");
		//spanCharsLeft.id = 'spanCharsLeft';
		//spanCharsLeft.style.fontSize = '12px';
		//spanCharsLeft.appendChild(document.createTextNode(' \u00A0 Chars Left: '));
		//spanCharsLeft.title = 'Characters remaining before reaching maximum limit';
		//spanCharsLeft.style.fontWeight = 'bold';
		//spanCharsLeft.style.display = 'none';
		//var txtCharsLeft = (document.createTextNode(maxLogLen));
		//txtCharsLeft.id = 'txtCharsLeft';
		//spanCharsLeft.appendChild(txtCharsLeft);
		//spanCounter.appendChild(spanCharsLeft);
		//SetCntBtn();

		spanCtrls.appendChild(spanCounter);




        var commentstxt = document.getElementById("litDescrCharCount");
        var lnkSmileys = document.createElement("p");
        insertAfter(lnkSmileys, commentstxt);

		//  Change smiley button to open smiley bar.
 		lnkSmileys.href = 'javascript:void(0)';
		//lnkSmileys.addEventListener("click", fSmileyBarOnOff, true);
		//lnkSmileys.title = 'Open/Close the Smiley Bar';

		//  Create smiley bar.
		var sbTable = document.createElement('table');
        sbTable.style.visibility = 'none';
		var sbRow = document.createElement('tr');
		var sbCol = document.createElement('td');
		sbTable.appendChild(sbRow);
		sbRow.appendChild(sbCol);
		sbCol.id = 'sbCol';

		sbTable.style.width = '118px';
		sbTable.style.border = 'solid 1px rgb(68, 142, 53)';
		sbTable.style.marginTop = '10px';
		sbTable.style.MozBorderRadius = '5px';
		sbTable.style.backgroundColor = 'rgb(239, 239, 239)';

		var arSbImgs = ['icon_smile.gif', 'icon_smile_big.gif', 'icon_smile_cool.gif',
				'icon_smile_blush.gif', 'icon_smile_tongue.gif', 'icon_smile_evil.gif',
				'icon_smile_wink.gif', 'icon_smile_clown.gif', 'icon_smile_blackeye.gif',
				'icon_smile_8ball.gif', 'icon_smile_sad.gif', 'icon_smile_shy.gif',
				'icon_smile_shock.gif', 'icon_smile_angry.gif', 'icon_smile_dead.gif',
				'icon_smile_sleepy.gif', 'icon_smile_kisses.gif', 'icon_smile_approve.gif',
				'icon_smile_dissapprove.gif', 'icon_smile_question.gif'];
		var arSbTitles = ['smile', 'big smile', 'cool', 'blush', 'tongue', 'evil', 'wink', 'clown',
				'black eye', 'eightball', 'frown', 'shy', 'shocked', 'angry', 'dead', 'sleepy',
				'kisses', 'approve', 'disapprove', 'question'];
		var arSbCodes = ['[:)]', '[:D]', '[8D]', '[:I]', '[:P]', '[}:)]', '[;)]', '[:o)]', '[B)]', '[8]',
				'[:(]', '[8)]', '[:O]', '[:(!]', '[xx(]', '[|)]', '[:X]', '[^]', '[V]', '[?]'];
		for (var c = 0; c < arSbImgs.length; c++) {
			var sbLink = document.createElement("a");
			var sbImg = document.createElement("img");
			sbImg.src = '../images/icons/' + arSbImgs[c];
			sbImg.border = 0;
			if (NewRel) {
				sbLink.style.marginLeft = '5px';
				sbLink.style.marginRight = '5px';
			} else {
				sbLink.style.marginLeft = '6px';
				sbLink.style.marginRight = '6px';
			}
			sbLink.appendChild(sbImg);
			sbLink.title = arSbTitles[c];
			sbLink.setAttribute('code', arSbCodes[c]);
			sbLink.href = 'javascript:void(0)';
			sbLink.addEventListener("click", fSmileyClicked, true);
			sbCol.appendChild(sbLink);
		}
		var ctBreak = document.createElement('br');
		insertAfter(ctBreak, lnkSmileys);
		insertAfter(sbTable, ctBreak);

			lnkSmileys.setAttribute('status', true);
			sbTable.style.visibility = 'visible';

    }




//	Insert element after an existing element.
function insertAfter(newElement, anchorElement) {
	anchorElement.parentNode.insertBefore(newElement, anchorElement.nextSibling);
}

//  Insert element aheadd of an existing element.
function insertAheadOf(newElement, anchorElement) {
	anchorElement.parentNode.insertBefore(newElement, anchorElement);
}

function removeNode(node) {
    node.parentNode.removeChild(node);
}


//  Update checkmark whenever this tab gets focus, to keep all tabs in sync.
function fWinFocus() {
	fUpdateAutoVisit(false);
}

//  Change checkmarks and visit action based on stored trackers.
function fUpdateAutoVisit(UpdateDropdown) {
	//  Counter for number of auto-trackers checked.
	var atc = 0;
    var new_page = false;
	// Get list of auto-visiting trackers, and put in array.
	var arAutoTrackers = GM_getValue('AutoTrackers' + SignedInAs, '').split(',');
	for (var idx in arTrackers) {
		var cbox = document.getElementById('chkbox_' + arTrackers[idx]);
		var radios = jq("input[name=actions-" + arTrackers[idx] + "]:radio");
		var drop = document.getElementById(cbox.getAttribute('drpdwnctl', ''));
		var label = document.getElementById(cbox.getAttribute('for', ''));
		var span = document.getElementById(cbox.getAttribute('for_span', ''));
		var bSetToAuto = (arAutoTrackers.indexOf(arTrackers[idx]) >= 0);
		cbox.checked = bSetToAuto;
		if (bSetToAuto) {
			++atc;
            if (label && span) {
                // new page
                span.style.backgroundColor = '#3d76c5';
                span.style.color = '#ffffff';
                new_page = true;
            }
			if(UpdateDropdown) {
                var ev = document.createEvent('Events');
                ev.initEvent('change', true, true);
                if (drop) {
                    // old page
				    drop.value = drop.options[drop.options.length - 1].value;
                    drop.dispatchEvent(ev);
                }
                else if (radios) {
                    // new page
                    var radio_select = radios.filter('[value=75]');
                    radio_select.prop('checked', true);
                    radio_select[0].dispatchEvent(ev);
                }
			}
		}
        else {
            if (label && span) {
                // new page
                span.style.backgroundColor = '#ffffff';
                span.style.color = '#3d76c5';
            }
        }
	}
	//  Create Visit & Submit button.
	var e_VisSubBtn = document.getElementById("VisSubBtn");
	if (e_VisSubBtn) {
		if (atc === 0) {
			removeNode(e_VisSubBtn);
		} else {
			e_VisSubBtn.innerHTML = '<span>Visit ' + atc + ' trackables &amp; Post</span>';
			e_VisSubBtn.value = 'Visit ' + atc + ' & Submit';
		}
	} else {
		if (atc > 0) {
            var log_button = get_log_button();
            var log_button_parent = log_button.parentNode;

            if (new_page) {
		    	e_VisSubBtn = document.createElement("button");
			    e_VisSubBtn.className = 'btn btn-primary';
            }
            else {
			    e_VisSubBtn = document.createElement("input");
                e_VisSubBtn.className = 'Button';
            }
    			e_VisSubBtn.type = 'button';
    		e_VisSubBtn.id = "VisSubBtn";
			e_VisSubBtn.style.marginRight = '20px';
			e_VisSubBtn.innerHTML = '<span>Visit ' + atc + ' trackables &amp; Post</span>';
			e_VisSubBtn.value = 'Visit ' + atc + ' & Submit';
			e_VisSubBtn.disabled = log_button.disabled;

          	log_button_parent.insertBefore(e_VisSubBtn, log_button);

			e_VisSubBtn.addEventListener("mousedown", fVisSubBtnClicked, true);
		}
	}
}

//  If travel bugs, hide, and add link to show.
var e_LogBookPanel1_TBPanel = document.getElementById("ctl00_ContentBody_LogBookPanel1_TBPanel");
if (e_LogBookPanel1_TBPanel) {

    if (!bShowInventory) {
		e_LogBookPanel1_TBPanel.style.display = 'none';
	}
	e_tblTravelBugs = document.getElementById("tblTravelBugs");
	if (e_tblTravelBugs) {
        var s1 = '';
        var BugCount = (e_tblTravelBugs.rows.length - 2);
		if (BugCount != 1) { s1 = 's'; }
		var e_BugCountSpan = document.createElement("span");
		e_BugCountSpan.style.color = 'rgb(255, 0, 0)';
		e_BugCountSpan.style.fontWeight = 'bold';
		e_BugCountSpan.style.paddingRight = '40px';
		var e_BugCountLink = document.createElement("a");
		e_BugCountLink.href = 'javascript:void(0)';
		e_BugCountLink.title = 'Click to show/hide inventory';
		e_BugCountLink.addEventListener("click", ToggleInventory, true);
		e_BugCountLink.appendChild(document.createTextNode(BugCount + ' item' + s1 +
			' in inventory'));
		e_BugCountSpan.appendChild(e_BugCountLink);
		get_log_button().parentNode.insertBefore(e_BugCountSpan,
				get_log_button().parentNode.firstChild);
	}
}




//  Handler for Visit & Submit button. Update dropdowns and click submit button.
function fVisSubBtnClicked() {
    var log_button = get_log_button();
    log_button.focus();
	fUpdateAutoVisit(true);
	log_button.click();
}

function get_log_button() {
    var log_button = e_LogBookPanel1_LogButton;
    if (!log_button) // new page
        log_button = jq('.btn-submit')[0];
    return log_button;
}



//  When checkmarks changed, update list of stored trackers, and execute function to
//  update display.
function fAutoVisitChanged() {
	var arAutoTrackers = GM_getValue('AutoTrackers' + SignedInAs, '').split(',');
	var cbTracker = this.getAttribute('tracker');
	var idx  = arAutoTrackers.indexOf(cbTracker);
	var bInList = (idx >= 0);
	if (this.checked) {
		if (!bInList) { arAutoTrackers.push(cbTracker); }
	} else {
		if (bInList) { arAutoTrackers.splice(idx, 1); }
	}
	GM_setValue('AutoTrackers' + SignedInAs, arAutoTrackers.join(','));
	fUpdateAutoVisit(false);
}

//  Show/hide inventory.
function ToggleInventory() {
	bShowInventory = (!bShowInventory);
	if (bShowInventory) {
		e_LogBookPanel1_TBPanel.style.display = '';
	} else {
		e_LogBookPanel1_TBPanel.style.display = 'none';
	}
}


	function fSmileyClicked() {
		var sCode = this.getAttribute('code');

		//  Autotrim selection;
		//fAutoTrim();

		//  Reset work fields.
		var nTxt = '';

		//  Save original text value and scroll position.
		var oTxt = e_LogTextArea.value;
		var txtTop = e_LogTextArea.scrollTop;

		//  If portion of the text selected, save that text.
		var selStart = e_LogTextArea.selectionStart;
		var selEnd = e_LogTextArea.selectionEnd;
		//  Get text on each side of the selection/carat;
		var txt1 = oTxt.substr(0, selStart);
		var txt2 = oTxt.substr(selEnd);
		//  Create new text and insert into text area.
		nTxt = txt1 + sCode + txt2;
		e_LogTextArea.value = nTxt;
		//  Position carot, based on if any text was selected.
		var newCaretPos = selStart + sCode.length;
		e_LogTextArea.focus();
		e_LogTextArea.selectionStart = newCaretPos;
		e_LogTextArea.selectionEnd = newCaretPos;
		e_LogTextArea.scrollTop = txtTop;
		// TextChg();
	}


function new_page_trackables(node) {
    var button_groups = node.find('div.actions');
    //GM_log("groups found: " + button_groups.length);
    
    if (!button_groups.length)
        return false;
    var button_group = button_groups[0];

    var tb_code = button_group.firstChild.nextSibling.firstChild.nextSibling.name.replace('actions-', '');

    var avTracker = tb_code;
    //var avDrpDwn = e_bodyTravelBugs.rows[r].cells[2].childNodes[1].id;
    arTrackers.push(avTracker);
    //var avCol = document.createElement('td');
    avCol = button_group;
    //insertAfter(avCol, e_bodyTravelBugs.rows[r].cells[cp]);
    var avChkBox = document.createElement('input');
    avChkBox.type = 'checkbox';
    avChkBox.id = 'chkbox_' + avTracker;
    avChkBox.setAttribute('tracker', avTracker);
    avChkBox.setAttribute('for', 'label_' + avTracker);
    avChkBox.setAttribute('for_span', 'span_' + avTracker);
    //avChkBox.setAttribute('row', r);
    //avChkBox.setAttribute('drpdwnctl', avDrpDwn);
    avChkBox.addEventListener("click", fAutoVisitChanged, true);
    avChkBox.style.height = '1px';
    avChkBox.style.width = '1px';
    avChkBox.style.margin = '-1px';
    avChkBox.style.padding = 0;
    avChkBox.style.border = 0;
    avChkBox.style.overflow = 'hidden';
    var avCbLbl = document.createElement('label');
    avCbLbl.setAttribute('for', 'chkbox_' + avTracker);
    avCbLbl.id = 'label_' + avTracker;
    //avCbLbl.appendChild(document.createTextNode('Auto-Visit'));
    var new_span = document.createElement('span');
    new_span.setAttribute('class', 'label');
    new_span.id = "span_" + avTracker;
    new_span.appendChild(document.createTextNode('Auto-Visit'));
    avCbLbl.appendChild(avChkBox);
    avCbLbl.appendChild(new_span);
    button_group.appendChild(avCbLbl);


    fUpdateAutoVisit(false);

    return false;
}