dfxdfx.at / Geocaching TB auto-visit

// ==UserScript==
// @name         Geocaching TB auto-visit
// @namespace    https://openuserjs.org/scripts/dfxdfx.at/Geocaching_TB_auto-visit
// @version      3.4
// @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
// @license      MIT
// ==/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);
  };
}

var SignedInAs = serverParameters["user:info"].username;
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;
}