MegaHelper

Helper to choose best Mega Evolution for Pokemon Go Events.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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();
})();