MegaHelper

Helper to choose best Mega Evolution for Pokemon Go Events.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         MegaHelper
// @namespace    http://tampermonkey.net/
// @version      20251209
// @description  Helper to choose best Mega Evolution for Pokemon Go Events.
// @author       MichaelWingull
// @match        https://leekduck.com/events/*/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=leekduck.com
// @license MIT
// ==/UserScript==

const variants = {
  "Galarian": "_GALARIAN",
  "Alolan": "_ALOLA",
  "Hisuian": "_HISUIAN"
}

const hiddenBoosts = {
  "GROUDON_PRIMAL": ["POKEMON_TYPE_GRASS"],
  "KYOGRE_PRIMAL": ["POKEMON_TYPE_ELECTRIC", "POKEMON_TYPE_BUG"],
  "RAYQUAZA_MEGA": ["POKEMON_TYPE_PSYCHIC"]
}

//ignore megas with less than MIN_BOOST boosts
const MIN_BOOST = 2

var eventSpawns

// This whole section is grabbed from https://code.google.com/p/jsonpath/
// Not sure if i can load it a better way

function jsonPath(obj, expr, arg) {
   var P = {
      resultType: arg && arg.resultType || "VALUE",
      result: [],
      normalize: function(expr) {
         var subx = [];
         return expr.replace(/[\['](\??\(.*?\))[\]']/g, function($0,$1){return "[#"+(subx.push($1)-1)+"]";})
                    .replace(/'?\.'?|\['?/g, ";")
                    .replace(/;;;|;;/g, ";..;")
                    .replace(/;$|'?\]|'$/g, "")
                    .replace(/#([0-9]+)/g, function($0,$1){return subx[$1];});
      },
      asPath: function(path) {
         var x = path.split(";"), p = "$";
         for (var i=1,n=x.length; i<n; i++)
            p += /^[0-9*]+$/.test(x[i]) ? ("["+x[i]+"]") : ("['"+x[i]+"']");
         return p;
      },
      store: function(p, v) {
         if (p) P.result[P.result.length] = P.resultType == "PATH" ? P.asPath(p) : v;
         return !!p;
      },
      trace: function(expr, val, path) {
         if (expr) {
            var x = expr.split(";"), loc = x.shift();
            x = x.join(";");
            if (val && val.hasOwnProperty(loc))
               P.trace(x, val[loc], path + ";" + loc);
            else if (loc === "*")
               P.walk(loc, x, val, path, function(m,l,x,v,p) { P.trace(m+";"+x,v,p); });
            else if (loc === "..") {
               P.trace(x, val, path);
               P.walk(loc, x, val, path, function(m,l,x,v,p) { typeof v[m] === "object" && P.trace("..;"+x,v[m],p+";"+m); });
            }
            else if (/,/.test(loc)) { // [name1,name2,...]
               for (var s=loc.split(/'?,'?/),i=0,n=s.length; i<n; i++)
                  P.trace(s[i]+";"+x, val, path);
            }
            else if (/^\(.*?\)$/.test(loc)) // [(expr)]
               P.trace(P.eval(loc, val, path.substr(path.lastIndexOf(";")+1))+";"+x, val, path);
            else if (/^\?\(.*?\)$/.test(loc)) // [?(expr)]
               P.walk(loc, x, val, path, function(m,l,x,v,p) { if (P.eval(l.replace(/^\?\((.*?)\)$/,"$1"),v[m],m)) P.trace(m+";"+x,v,p); });
            else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) // [start:end:step]  phyton slice syntax
               P.slice(loc, x, val, path);
         }
         else
            P.store(path, val);
      },
      walk: function(loc, expr, val, path, f) {
         if (val instanceof Array) {
            for (var i=0,n=val.length; i<n; i++)
               if (i in val)
                  f(i,loc,expr,val,path);
         }
         else if (typeof val === "object") {
            for (var m in val)
               if (val.hasOwnProperty(m))
                  f(m,loc,expr,val,path);
         }
      },
      slice: function(loc, expr, val, path) {
         if (val instanceof Array) {
            var len=val.length, start=0, end=len, step=1;
            loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, function($0,$1,$2,$3){start=parseInt($1||start);end=parseInt($2||end);step=parseInt($3||step);});
            start = (start < 0) ? Math.max(0,start+len) : Math.min(len,start);
            end   = (end < 0)   ? Math.max(0,end+len)   : Math.min(len,end);
            for (var i=start; i<end; i+=step)
               P.trace(i+";"+expr, val, path);
         }
      },
      eval: function(x, _v, _vname) {
         try { return $ && _v && eval(x.replace(/@/g, "_v")); }
         catch(e) { throw new SyntaxError("jsonPath: " + e.message + ": " + x.replace(/@/g, "_v").replace(/\^/g, "_a")); }
      }
   };

   var $ = obj;
   if (expr && obj && (P.resultType == "VALUE" || P.resultType == "PATH")) {
      P.trace(P.normalize(expr).replace(/^\$;/,""), obj, "$");
      return P.result.length ? P.result : false;
   }
}

// default stolen block on how to get data
// maybe cooler to get it with await

var getJSON = function(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'json';
    xhr.onload = function() {
      var status = xhr.status;
      if (status === 200) {
        callback(null, xhr.response);
      } else {
        callback(status, xhr.response);
      }
    };
    xhr.send();
};

async function loadPokeData(){
    // was planning to cache the data into local browser string variable but i got fartbuckled
    // by JS callback hell only giving me promises instead of just the god dang string
    getJSON('https://pokemon-go-api.github.io/pokemon-go-api/api/pokedex.json',
            function(err, data) {
        if (err !== null) {
            alert('Something went wrong: ' + err);
        } else {
            processPokeData(data)
        }
    });
}

function checkTypes(currentMega, types){
    if (types.includes(currentMega.primaryType.type) ||
        currentMega.secondaryType != null && types.includes(currentMega.secondaryType.type) ||
        hiddenBoosts.hasOwnProperty(currentMega.id) && types.includes(hiddenBoosts[currentMega.id])
    ) {
        return true
    }
    return false
}

function processPokeData(data) {
    eventSpawns = getEventSpawns(data)
    var megaMatches = []
    var megas = jsonPath(data, "$[*].megaEvolutions.*")
    for (var i = 0; i < megas.length; ++i) {
        var hits = []
        for (var j = 0; j < eventSpawns.length; ++j) {
            if (checkTypes(megas[i], eventSpawns[j].types)) {
                hits.push(eventSpawns[j])
            }
        }
        if (hits.length>=MIN_BOOST) {
            megaMatches.push({
                name: megas[i].names.English,
                hits: hits
            })
        }

    }
    megaMatches.sort(function(a, b){
        return b.hits.length-a.hits.length
    })
    insertHTML(megaMatches)
}

function insertHTML(megaMatches) {
    //this is a complete hacky mess for the proof of concept
    var p = document.getElementById("spawns").nextSibling.nextSibling;
    p.innerHTML=""
    var list = document.createElement("ul")
    p.appendChild(list)
    for (var i = 0; i < megaMatches.length; ++i) {
        var item = document.createElement("li")
        item.appendChild(document.createTextNode(megaMatches[i].hits.length + " " + megaMatches[i].name))
        item.style["list-style-type"] = "none"
        item.style.float = "left"
        item.style.border = "1px solid black"
        item.style.margin = "2px"
        item.match = megaMatches[i]
        list.appendChild(item)
        item.onmouseover = function(){
            this.style["background-color"] = "grey"
            for (var j = 0; j < this.match.hits.length; ++j) {
                highLight(this.match.hits[j].listElement)
            }
        };
        item.onmouseout = function(){
             this.style.removeProperty("background-color")
            for (var j = 0; j < this.match.hits.length; ++j) {
                downLight(this.match.hits[j].listElement)
            }
        };
    }
    var breaker = document.createElement("p");
    breaker.style.display = "block";
    breaker.style.clear = "both";
    p.appendChild(breaker)
}

function getFormId(name) {
  var formId = name.toUpperCase().replace("-","_");
  var isVariant = false
  for (const [key, value] of Object.entries(variants)) {
    if (name.startsWith(key)) {
      formId = formId.slice(key.length+1)+value
      isVariant = true
    }
  }
  return [formId, isVariant]
}

function getPkmnNamesFromUL(list, data) {
    var items = list.getElementsByTagName("li");
    var result = []
    for (var i = 0; i < items.length; ++i) {
        var name = items[i].getElementsByClassName("pkmn-name")[0].innerText
        const [formId, isVariant] = getFormId(name)

        var jPath = isVariant ? "$.[?(@.formId=='"+formId+"')]" : "$[?(@.formId=='"+formId+"')]"
        console.log(jPath)
        var pkmn = jsonPath(data, jPath)[0]
        var types = [pkmn.primaryType.type]

        if (pkmn.secondaryType != null) {
            types.push(pkmn.secondaryType.type);
        }

        result.push({
            name: name,
            listElement: items[i],
            types: types
        })
    }
    return result
}

function getEventSpawns(data){
    var xpath = '//h2[@id="spawns"]/following-sibling::h2[contains(@class, "event-section-header")][1]/preceding-sibling::ul[count(.|//h2[@id="spawns"]/following-sibling::ul) = count(//h2[@id="spawns"]/following-sibling::ul)]';
    var nodes = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null)
    var result = nodes.iterateNext();
    var eventSpawns = []
    while (result) {
        eventSpawns = eventSpawns.concat(getPkmnNamesFromUL(result, data))
        result = nodes.iterateNext();
    }
    return eventSpawns
}

function highLight(element) {
    var nameElement = element.getElementsByClassName("pkmn-name")[0]
    nameElement.style["font-weight"] = "bolder";
    nameElement.style.color= "antiquewhite";
    nameElement.style["text-shadow"] = "-1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000";
    var imgElement = element.childNodes[0].style["background-color"] = "red";
}

function downLight(element) {
    var nameElement = element.getElementsByClassName("pkmn-name")[0]
    nameElement.style.removeProperty("font-weight")
    nameElement.style.removeProperty("color");
    nameElement.style.removeProperty("text-shadow")
    element.childNodes[0].style.removeProperty("background-color")
}

(function() {
    'use strict';
    loadPokeData();
})();