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