MegaHelper

Helper to choose best Mega Evolution for Pokemon Go Events.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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