Reversewigo solver ✓🐵

Reversewigo solver ✓🐵 - solve reverse wherigo automatically on cache page. Solver code originated from Rick Rickhardson's reverse-wherigo source (public domain). Also, creates show source button for mystery cacahes.

2021/05/22のページです。最新版はこちら

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name        Reversewigo solver ✓🐵
// @namespace    http://tampermonkey.net/
// @version      0.12
// @description  Reversewigo solver ✓🐵  - solve reverse wherigo automatically on cache page. Solver code originated from Rick Rickhardson's reverse-wherigo source (public domain). Also, creates show source button for mystery cacahes.
// @author       [email protected]
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_setClipboard
// @include      https://www.geocaching.com/geocache/*
// @require      https://openuserjs.org/src/libs/sizzle/GM_config.js
// ==/UserScript==

// placeholder for calculated reverse wherigo coordinates
// used by pollDOM to update them to popup dialog
// TODO: definitely not the best practise but a quick hack that seems to work (for now)
var newCoordinates = "";

GM_registerMenuCommand("Configure", () => { GM_config.open() }, "C");

GM_config.init(
    {
      'id': 'MyConfig', // The id used for this instance of GM_config
      'title': 'Script Settings', // Panel Title
      'fields': // Fields object
      {
        'OutputDD':
        {
        'label': 'Show dd (decimal degrees) format (60.12345 25.4321) coordinates in output', // Appears next to field
        'type': 'checkbox', // Makes this setting a checkbox input
        'default': false // Default value if user doesn't change it
        },
        'OutputCodes':
        {
        'label': 'Show wherigo codes in output', // Appears next to field
        'type': 'checkbox', // Makes this setting a checkbox input
        'default': false // Default value if user doesn't change it
        }
        ,
        'OutputDistance':
        {
        'label': 'Show distance to bogus in output', // Appears next to field
        'type': 'checkbox', // Makes this setting a checkbox input
        'default': false // Default value if user doesn't change it
        }
      }
    });

/* async */ function dd2dm(latdd, londd) {
    var lat = latdd;
    var lon = londd;

    var latResult, lonResult, dmsResult;

    lat = parseFloat(lat);
    lon = parseFloat(lon);

    latResult = /* await */ lat_dd2dm(lat);
    lonResult = /* await */ lon_dd2dm(lon);

    // Joining both variables and separate them with a space.
    dmsResult = latResult + ' ' + lonResult;

    // Return the resultant string
    return { result: dmsResult, lat: latResult, lon: lonResult };
}

/* async */ function lat_dd2dm(val) {
    var valDeg, valMin, result;

    val = Number(val);
    result = (val >= 0)? 'N' : 'S';
    result += " ";

    val = Math.abs(val);
    valDeg = Math.floor(val);

    if( valDeg < 10 ) {
    result += "0" + valDeg;
    }
    else {
    result += valDeg;
    }
    result += "\xB0";
    result += " ";

    valMin = ((val - valDeg) * 60).toFixed(3);
    if( valMin < 10) {
        result += "0" + valMin;
    }
    else {
        result += valMin;
    }
    return result;
}

/* async */ function lon_dd2dm(val) {
    var valDeg, valMin, result;

    val = Number(val);

    result = (val >= 0)? 'E' : 'W';
    result += " ";

    val = Math.abs(val);
    valDeg = Math.floor(val);

    if( valDeg < 10 ) {
        result += "00" + valDeg;
    }
    else if( valDeg < 100) {
        result += "0" + valDeg;
    }
    else {
        result += valDeg;
    }
    result += "\xB0";
    result += " ";

    valMin = ((val - valDeg) * 60).toFixed(3);
    if( valMin < 10) {
        result += "0" + valMin;
    }
    else {
        result += valMin;
    }
    return result;
}

/* async */ function code2LatLon(varA, varB, varC) {
    let latSign, lonSign, lonValue, latValue;

    console.debug("Converting [" + varA + ", " + varB + ", " + varC + "] to LatLon" )

    // 123456 => digit 1 (d1) = 6; digit 2 (d2) = 5; ...
    // syntax for varA => digit 1 var A = A1; digit 2 varA = A2; ...
    // A3
    if ((varA % 1000 - varA % 100) / 100 == 1) {
        latSign = 1;
        lonSign = 1;
    }
    // A3
    else if ((varA % 1000 - varA % 100) / 100 == 2) {
        latSign = -1;
        lonSign = 1;
    }
    // A3
    else if ((varA % 1000 - varA % 100) / 100 == 3) {
        latSign = 1;
        lonSign = -1;
    }
    // A3
    else if ((varA % 1000 - varA % 100) / 100 == 4) {
        latSign = -1;
        lonSign = -1;
    }
//T41140 / Q1TQ01 / 14S4RS
    // A6 B3 B4 B6 C1 C2 C4
    // TODO: how to iterate only these, not full range ??
    // C (d5 + d2) eli C5 + C2 = parillinen
    if ( ((varC % 100000 - varC % 10000) / 10000 + (varC % 100 - varC % 10) / 10) % 2 === 0) {
        // A4 B2  B5 C3 A6 C2 A1
        latValue = Number(((varA % 10000 - varA % 1000) / 1000 * 10 + (varB % 100 - varB % 10) / 10 + (varB % 100000 - varB % 10000) / 10000 * 0.1 + (varC % 1000 - varC % 100) / 100 * 0.01 + (varA % 1000000 - varA % 100000) / 100000 * 0.001 + (varC % 100 - varC % 10) / 10 * 1.0E-4 + varA % 10 * 1.0E-5));
        // A5 C6 C1  B3 B6 A2 C5 B1
        lonValue = Number(((varA % 100000 - varA % 10000) / 10000 * 100 + (varC % 1000000 - varC % 100000) / 100000 * 10 + varC % 10 + (varB % 1000 - varB % 100) / 100 * 0.1 + (varB % 1000000 - varB % 100000) / 100000 * 0.01 + (varA % 100 - varA % 10) / 10 * 0.001 + (varC % 100000 - varC % 10000) / 10000 * 1.0E-4 + varB % 10 * 1.0E-5));
    }
    // C (d5 + d2) eli C5+C2= pariton
    else if ( ((varC % 100000 - varC % 10000) / 10000 + (varC % 100 - varC % 10) / 10) % 2 !== 0) {
        // B6 A1  A4 C6 C3 C2 A6
        latValue = Number(((varB % 1000000 - varB % 100000) / 100000 * 10 + varA % 10 + (varA % 10000 - varA % 1000) / 1000 * 0.1 + (varC % 1000000 - varC % 100000) / 100000 * 0.01 + (varC % 1000 - varC % 100) / 100 * 0.001 + (varC % 100 - varC % 10) / 10 * 1.0E-4 + (varA % 1000000 - varA % 100000) / 100000 * 1.0E-5))
        // B2 C1 A2  A5 B3 B1 C5 B5
        lonValue = Number(((varB % 100 - varB % 10) / 10 * 100 + varC % 10 * 10 + (varA % 100 - varA % 10) / 10 + (varA % 100000 - varA % 10000) / 10000 * 0.1 + (varB % 1000 - varB % 100) / 100 * 0.01 + varB % 10 * 0.001 + (varC % 100000 - varC % 10000) / 10000 * 1.0E-4 + (varB % 100000 - varB % 10000) / 10000 * 1.0E-5));
    }
    // B4 C4 = ALWAYS ignore

    latValue = latSign * latValue;
    lonValue = lonSign * lonValue;

    return { lat: latValue, lon: lonValue }
}

function fix(v, left, right) {
    return (v < 0 ? '-' : ' ') + Math.abs(v).toFixed(right).padStart(left + right + 1, '0');
}

function haversineDistance(lat1,lon1,lat2,lon2) {
    const R = 6371e3; // metres
    const φ1 = lat1 * Math.PI/180; // φ, λ in radians
    const φ2 = lat2 * Math.PI/180;
    const Δφ = (lat2-lat1) * Math.PI/180;
    const Δλ = (lon2-lon1) * Math.PI/180;

    const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
              Math.cos(φ1) * Math.cos(φ2) *
              Math.sin(Δλ/2) * Math.sin(Δλ/2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

    const d = R * c; // in metres
    return d;
  }

  function dm2dd( nsew, deg, min ) {
      let sign;
      if( nsew == 'N' || nsew == 'E' ) {
          sign = 1;
      }
      else {
          sign = -1;
      }

      let numdeg = parseInt(deg);
      let nummin = parseFloat(min);

      let dd = sign * (numdeg + nummin/60);
      return dd;
  }


function createShowSourceButton() {
    let mybutton = document.createElement("srcbutton");
    mybutton.innerHTML = "View source";
    mybutton.setAttribute("style", "-webkit-appearance: button;-moz-appearance: button;appearance: button;  background-color: green; color: white; border: none; text-align: center;  display: inline-block; padding: 3px 10px; border: 1px solid black; cursor: pointer;");
    let noteref = document.getElementsByClassName("minorCacheDetails Clear")[0];
    noteref.appendChild(mybutton);

    mybutton.addEventListener("click", function() {
        var desc1 = document.getElementById("ctl00_ContentBody_ShortDescription").innerHTML;
        var desc2 = document.getElementById("ctl00_ContentBody_LongDescription").innerHTML;
        alert( desc1 + desc2 )
    });
}

function createShowSolveREReverseButton() {
//    let mybutton = document.createElement("solvebutton");
    let mybutton = document.createElement("a");
    mybutton.innerHTML = "Solve Regexp";

    mybutton.className = "mybtn"
    //css
    mybutton.setAttribute("style", "-webkit-appearance: button;-moz-appearance: button;appearance: button;  background-color: green; color: white; border: none; text-align: center;  display: inline-block; padding: 3px 10px; border: 1px solid black; cursor: pointer;");
    

    let noteref = document.getElementsByClassName("minorCacheDetails Clear")[0];
    noteref.appendChild(mybutton);

    mybutton.addEventListener("click", function() {
        solveReverseRE();
    });

    /*
    mybutton.addEventListener("click", function() {
        $( function() {
            $( "#dialog" ).dialog({width: "auto"})
            .next(".ui-widget-overlay")
            .css({
                opacity: 1.0,
                filter: "Alpha(Opacity=100)",
                backgroundColor: "black"
            });
          } );
        solveReverseRE();
    });
    */
}

function createDialog( ) {
    let mydialog = document.createElement("div");
    mydialog.id = "dialog";
    mydialog.title = "Results";
    let resultmsg = document.createElement("p");
    resultmsg.id = "resultmsg";
    mydialog.appendChild(resultmsg);
//    let noteref = document.getElementsByClassName("minorCacheDetails Clear")[0];
    let noteref = document.getElementById("ctl00_ContentBody_detailWidget");
    noteref.appendChild(mydialog);
}

// TODO: definitely not the best practice, but works (for now)
function pollDOM() {
    const newCrd = document.getElementById('newCoordinates');

    if (newCrd != null) {
      // Do something with el
      newCrd.value = newCoordinates;

      let submitBtn = document.getElementsByClassName("btn-cc-parse")[0];
      //ssubmitBtn.click();
      } else {
      setTimeout(pollDOM, 300); // try again in 300 milliseconds
    }
}

function updateCoordinates( newCoords ) {
    let crd = document.getElementById("uxLatLonLink");
    crd.click();

    newCoordinates = newCoords;

    pollDOM();
    console.debug("XX: " + newCoords);
}

// retrieve bogus coordinates in ddm
// returns array [ lat side (N/S), lat deg, lat mindec, lon side (E/W), lon deg, lon mindec ];
function getBogusCoordsFromPage() {
    let coords = document.getElementById("uxLatLon").textContent.split(" ");
    if( coords.length != 6 ) {
        console.error("Error parsing coordinates");
        return;
    }
    return coords;
}

// convert latitude longitude array [ lat side (N/S), lat deg, lat mindec, lon side (E/W), lon deg, lon mindec ] to dd
// return { lat: latitude dd , lon: longitude dd }
function latLonDmArr2dd( latLonDmArray ) {
    // convert bogus coordinates from deg-mindec to deg-dec
    let bogusLat = dm2dd( latLonDmArray[0], latLonDmArray[1], latLonDmArray[2] );
    let bogusLon = dm2dd( latLonDmArray[3], latLonDmArray[4], latLonDmArray[5] );
    return { lat : bogusLat, lon : bogusLon }
}

// htmlsnip = html snippet in string
// replace = boolean; true = replace existing html fully
function addHTMLtoPage( htmlsnip, replace ) {
    let docEl = document.getElementsByClassName("Note Disclaimer")[0];

    if( replace ) {
        docEl.innerHTML = htmlsnip;
    }
    else {
        docEl.innerHTML = docEl.innerHTML + htmlsnip;
    }
}

function onlyUnique(value, index, self) {
  return self.indexOf(value) === index;
}

function* makeRangeIterator(start = 0, end = 100, step = 1) {
    let iterationCount = 0;
    for (let i = start; i < end; i += step) {
        iterationCount++;
        yield i;
    }
    return iterationCount;
}

function* makeRangeIterator2(variables = 1, start = 0, end = 100, step = 1) {
    let iterationCount = 0;
    for (let i = start; i < end; i += step) {
        iterationCount++;
        yield i;
    }
    return iterationCount;
}

function testGenerator() {
    const it = makeRangeIterator(0, 9, 1);

    let result = it.next();
    while (!result.done) {
        console.log(result.value); // 0 1 2 3 4 5 6 7 8 9
        result = it.next();
    }

}

// Works in Chrome devtools console
function* codeGenerator ( codes, vars ) {
    // check if no variables left => return final codes
    console.debug("Vars.length = " + vars.length);
            
    if( vars == null || vars.length == 0 ) {
        console.debug("Yield" + JSON.stringify(codes));
        yield codes;
    }
    else {
        // generate regexp to replace VAR => number
        let replRE = new RegExp("/"+vars[0]+"/g");
        console.debug("RE " + JSON.stringify(replRE));

        // for each var value [0,9]
        for(let i=0; i<10; i++ ) {
            let c = [];
            c[0] = codes[0].replace(vars[0], i.toString());
            c[1] = codes[1].replace(vars[0], i.toString());
            c[2] = codes[2].replace(vars[0], i.toString());
            console.debug("/"+vars[0]+"/g -> " + JSON.stringify(c));
            // recursively get the other variables processed
            yield* codeGenerator( c, vars.slice(1) );
        }
    }
}

// replace char at position index for string with replacement
String.prototype.replaceAt = function(index, replacement) {
    return this.substr(0, index) + replacement + this.substr(index + replacement.length);
}

/* async */ function solveReverseRE() {
    console.debug("Attempting to parse codes from note.");
    let resultmsg = document.getElementsByClassName("Note Disclaimer")[0];

    let codes = parseCodesFromNote();
    if(codes.length != 3) {
        console.error("Error: >3 or <3 codes found:" + JSON.stringify(codes));
    }

    // cleanse
    for(let i=0; i<3; i++) {
      codes[i] = codes[i].trim();
    }
    console.debug("Parsed: " + JSON.stringify(codes));
    // TODO: remove vars from non meaningful items : B4 C4 = ALWAYS ignore
    console.log("Emptying non-meaninful digits B4 C4");
    codes[1] = codes[1].replaceAt( 6-4, "0"); //reset B4
    codes[2] = codes[2].replaceAt( 6-4, "0"); //reset C4

    console.debug("Cleansed: " + JSON.stringify(codes));

    // calculate # of variables/unknowns per code
    let vars = [];
    for(let i=0; i<3; i++) {
        let matching = codes[i].match(/[A-Za-z]/g);
        if(matching != null)
            vars = vars.concat( matching );
    }
    console.debug("Vars: " + JSON.stringify(vars));
    let uniqVars = vars.filter(onlyUnique);

    console.log(uniqVars.length + " unique variables");
    console.warn("Possible total combos: " + 10**uniqVars.length);

    let bogusLatLonArr = getBogusCoordsFromPage();
    // convert bogus coordinates from deg-min-dec to deg-dec
    let bogusDD = latLonDmArr2dd(bogusLatLonArr);
    let bogusLat = bogusDD.lat;
    let bogusLon = bogusDD.lon;

    console.debug("Parsed coords: " + JSON.stringify(bogusLatLonArr));
    console.debug("Converted coords: " + bogusLat + "  " + bogusLon);
    console.debug("Iterating...");

    console.log("code1,code2,code3,lat(dd),lon(dd),lat(gps),lon(gps),dist(m)");
    let counter = 0;

    let codeGen = codeGenerator( codes, uniqVars );

    let nextCode = codeGen.next();
    console.debug(JSON.stringify(nextCode));
    while (!nextCode.done) {
        // convert to dd
        let ddlatlon = code2LatLon( parseInt( nextCode.value[0] ), parseInt( nextCode.value[1] ) , parseInt( nextCode.value[2]) );
        console.debug("Convertion results: " + JSON.stringify(ddlatlon));
        if( ddlatlon.lat == NaN || ddlatlon.lon == NaN ) continue;
        else {
            let dist = haversineDistance( bogusLat, bogusLon, ddlatlon.lat, ddlatlon.lon );
            if( dist < 3220 ) {
                counter = counter+1;
                let dmlatlon = /* await */ dd2dm( ddlatlon.lat, ddlatlon.lon);
                let result = nextCode.value[0] + "," + nextCode.value[1] + "," + nextCode.value[2] + "," + fix(ddlatlon.lat, 2, 6) + "," + fix(ddlatlon.lon, 3, 6) + "," + dmlatlon.lat + "," + dmlatlon.lon + "," + dist.toFixed(0) ;
                console.log(result);

                let result2 = "";
                // if config output DD on, print also DD coords to output (makes it easier to deduce some variables)
                if( GM_config.get('OutputDD') ) {
                    result2 = fix(ddlatlon.lat, 2, 6) + " " + fix(ddlatlon.lon, 3, 6) + " | ";
                }
                // default output
                result2 = result2 + dmlatlon.lat + " " + dmlatlon.lon;
                // if config option OutputCodes = true, output also calculated wigo codes
                if( GM_config.get('OutputCodes') ) {
                    result2 = result2 + " [" + nextCode.value[0] + "," + nextCode.value[1] + "," + nextCode.value[2] + "]" ;
                }
                if( GM_config.get('OutputDistance') ) {
                    result2 = result2 + " ⟼ " +  dist.toFixed(0) + " m";
                }
                resultmsg.innerText = resultmsg.innerText + "\n" + result2;
            }
            /* else {
                let dmlatlon = dd2dm( ddlatlon.lat, ddlatlon.lon);
                let result = nextCode.value[0] + "," + nextCode.value[1] + "," + nextCode.value[2] + "," + fix(ddlatlon.lat, 2, 6) + "," + fix(ddlatlon.lon, 3, 6) + "," + dmlatlon.lat + "," + dmlatlon.lon + "," + dist.toFixed(0) + "out of range" ;
                console.warn(result);
            } */
        }

        nextCode = codeGen.next();
    }
    console.log("Total hits in range: " + counter);

}



function addBogusDDToPage() {
    let bogusLatLonArr = getBogusCoordsFromPage();
    // convert bogus coordinates from deg-min-dec to deg-dec
    let bogusDD = latLonDmArr2dd(bogusLatLonArr);
    let bogusLat = bogusDD.lat;
    let bogusLon = bogusDD.lon;

    console.debug("Parsed coords: " + JSON.stringify(bogusLatLonArr));
    console.debug("Converted coords: " + bogusLat + "  " + bogusLon);

    // add bogus dd coordinates on page
    addHTMLtoPage( "<strong> Bogus: " + bogusLat + "  " + bogusLon + " </strong>", true );

    /*let locnode = document.getElementById("ctl00_ContentBody_LocationSubPanel");
    locnode.textContent = bogusLat + "  " + bogusLon;
    locnode.appendChild( document.createElement("br"));
*/

}

// takes wigo codes as strings (may consist not numbers (variables) e.g. 022345 042x21 924ab9)
// returns resulting coordinate (DD) string
function evalPartialReverseWigo(codeA, codeB, codeC) {
    // validate that these are numbers
    console.debug("Input: " + codeA + ", " + codeB + ", " + codeC);
    if( isNaN( codeC[6-5] ) == false && isNaN( codeC[6-2] ) == false ) {
        // C5 + C2 = parillinen
        if( (Number(codeC[6-5]) + Number(codeC[6-2])) % 2 === 0 ) {
            console.debug("parillinen");
            // A4 B2  B5 C3 A6 C2 A1
            let resLat = "" + codeA[6-4] + codeB[6-2] + "." + codeB[6-5] + codeC[6-3] + codeA[6-6] + codeC[6-2] + codeA[6-1];
            // A5 C6 C1  B3 B6 A2 C5 B1
            let resLon = "" + codeA[6-5] + codeC[6-6] + codeC[6-1] + "." + codeB[6-3] + codeB[6-6] + codeA[6-2] + codeC[6-5] + codeB[6-1];
            console.debug(resLat + " " + resLon);
            return resLat + " " + resLon;
        }
        // C (d5 + d2) eli C5+C2= pariton
        else if ( (Number(codeC[6-5]) + Number(codeC[6-2])) %2 !== 0 ) {
            console.debug("pariton");
            // B6 A1  A4 C6 C3 C2 A6
            let resLat = "" + codeB[6-6] + codeA[6-1] + "." + codeA[6-4] + codeC[6-6] + codeC[6-3] + codeC[6-2] + codeA[6-6];
            // B2 C1 A2  A5 B3 B1 C5 B5
            let resLon = "" + codeB[6-2] + codeC[6-1] + codeA[6-2] + "." + codeA[6-5] + codeB[6-3] + codeB[6-1] + codeC[6-5] + codeB[6-5];
            console.debug(resLat + " " + resLon);
            return resLat + " " + resLon;

        }
    }
    else {
        console.debug("parillinen tai pariton");
        console.warn("Cannot deduce which equation to use - showing both results");
            let resLat1 = "" + codeA[6-4] + codeB[6-2] + "." + codeB[6-5] + codeC[6-3] + codeA[6-6] + codeC[6-2] + codeA[6-1];
            // A5 C6 C1  B3 B6 A2 C5 B1
            let resLon1 = "" + codeA[6-5] + codeC[6-6] + codeC[6-1] + "." + codeB[6-3] + codeB[6-6] + codeA[6-2] + codeC[6-5] + codeB[6-1];

            let resLat2 = "" + codeB[6-6] + codeA[6-1] + "." + codeA[6-4] + codeC[6-6] + codeC[6-3] + codeC[6-2] + codeA[6-6];
            // B2 C1 A2  A5 B3 B1 C5 B5
            let resLon2 = "" + codeB[6-2] + codeC[6-1] + codeA[6-2] + "." + codeA[6-5] + codeB[6-3] + codeB[6-1] + codeC[6-5] + codeB[6-5];
            console.debug( resLat1 + " " + resLon1 + "\n OR \n" + resLat2 + " " + resLon2 );
            return resLat1 + " " + resLon1 + " <br>OR<br> " + resLat2 + " " + resLon2;
    }
    // A3 B4 C4 = ALWAYS ignore

}

// parse reverse wherigo codes from note
function parseCodesFromNote() {
    let re1 = /RWIGO:/g;
    let re2 = /[0-9A-Za-z]{6}/g;
    console.log("Trying to find codes from user note:");
    let text = document.getElementById("viewCacheNote").innerText;
    if( text.match( re1 ) ) {
        console.debug("Match found (RWIGO:)");
        let codes = text.match( re2 );
        console.debug( JSON.stringify( codes ) );
        return codes;
    }
    else {
        console.debug("No codes found from Personal Note");
        return null;
    }
}

// parse codes from cache description
function parseCodesFromDescription() {
    let re = /[0-9]{6}/g;
    let codes = document.getElementsByClassName("UserSuppliedContent")[1].innerText.match( re );
    return codes;
}

  (/* async */ function() {
    'use strict';
    let from, result;

    let cacheType = document.getElementsByClassName("cacheImage")[0].title;

    createShowSourceButton();
    addBogusDDToPage();



    if( cacheType == "Wherigo Cache" ) {
        createShowSolveREReverseButton();
        createDialog();

        let codes = parseCodesFromNote();
        // Codes found from Personal Note
        if( codes !== null && codes.length >= 3 ) {
            console.debug("Match: " + codes[0] + "," + codes[1] + "," + codes[2] );

            let reverseRes = evalPartialReverseWigo( codes[0], codes[1], codes[2] );
            addHTMLtoPage("<br><strong>Codes with variables converted to coordinates: <br>"+reverseRes+"</strong>", false);

            // TODO: how to check if code 2 coords conversion is valid => only show if it is
            let ddlatlon = /* await */ code2LatLon( codes[0], codes[1], codes[2] );
            let dmlatlon = /* await */ dd2dm( ddlatlon.lat, ddlatlon.lon);
            from = codes[0] + " " + codes[1] + " " + codes[2] + " => ";
            let resultDD = fix(ddlatlon.lat, 2, 6) + " " + fix(ddlatlon.lon, 3, 6);
            let resultDMD = dmlatlon.lat + " " + dmlatlon.lon;
            result = resultDMD;
            console.log( from + " " + resultDD + " = " + resultDMD );

            addHTMLtoPage("<br><strong> Reversewigo final coordinates =  " + result + " </strong>", false);
            if( result != "S NaN° NaN W NaN° NaN")
                updateCoordinates( resultDMD );
        }
        // No codes in Personal Note - trying to retrieve directly from description
        else {
            console.log("Trying to find codes from UserSuppliedContent");
            codes = parseCodesFromDescription();
            if( codes !== null && (codes.length == 3 || codes.length == 6 || codes.length == 9) ) {
                console.debug("Parsed codes from UserSuppliedContent: " + JSON.stringify(codes) );

                let reverseRes = evalPartialReverseWigo( codes[0], codes[1], codes[2] );
                addHTMLtoPage("<br><strong>Reverse results = "+reverseRes+"</strong>", false);

                let ddlatlon = /* await */ code2LatLon( codes[0], codes[1], codes[2] );
                let dmlatlon = /* await */ dd2dm( ddlatlon.lat, ddlatlon.lon);
                from = codes[0] + " " + codes[1] + " " + codes[2] + " => ";
                let resultDD = fix(ddlatlon.lat, 2, 6) + " " + fix(ddlatlon.lon, 3, 6);
                let resultDMD = dmlatlon.lat + " " + dmlatlon.lon;
                result = resultDMD;
                console.debug( from + " " + resultDD + " = " + resultDMD );

                addHTMLtoPage("<br><strong> Reversewigo final coordinates =  " + result + " </strong>", false);
                if( result != "S NaN° NaN W NaN° NaN") {
                    GM_setClipboard( result, "text" );
                    updateCoordinates( resultDMD );
                }
            }
            else {
                result = "Error: Reverse wherigo codes not found from description/note. <br>Enter codes to Personal Note as shown below, save and reload. <br>RWIGO:<br>123ABC<br>23D45G<br>423444";
                console.log( result );
                addHTMLtoPage("<br><strong>" + result + " </strong>", false);
            }
        }
    }
})();