IGDB List Extra Info

Adds additional information (genre, rating, keywords)to igdb.com lists. They can be loaded witha button // click. Needs an IGDB api key.

// ==UserScript==
// @name         IGDB List Extra Info
// @namespace    https://greasyfork.org/de/users/155913-nkay08
// @description  Adds additional information  (genre, rating, keywords)to igdb.com lists. They can be loaded witha button // click. Needs an IGDB api key.
//
// @author       NKay
// @include        http*://www.igdb.com/*
// @grant        none
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @version 0.0.3.201808121415
// ==/UserScript==


//insert your API-KEY here
var apikey = '';


var apiurl;
var req;
var reqid;
var corsproxy;
var fields;
var fieldsall


var myheaders;

var gametextarray;
var gameidarray;
var genresnodearray;
var keywordsnodearray;
var ratingnodearray;
var ttbnodearray;

var keyword_dict = {};
var genres_dict = {};
var id_list = {};
var genres_list_list = {};
var genres_list = {};
var keyword_list = {};
var games_info = {};
var games_text_info = {};

var idortext;

var keyelement = "div.content-left.col-md-pull-10.col-md-2";
//only execute function if this element is loaded via ajax
waitForKeyElements(keyelement, checkForElement);

//waitForKeyElements(keyelement,alert1);

function alert1() {
    alert('alert');
}

function init() {
    apiurl = 'https://api-endpoint.igdb.com';
    req = '/games/?search=';
    reqid = '/games/1942?fields=*';
    corsproxy = 'https://cors-anywhere.herokuapp.com/';
    fields = '?fields=genres,keywords,rating,rating_count,aggregated_rating,aggregated_rating_count,total_rating,total_rating_count,time_to_beat';
    fieldsall = 'fields=*';
    myheaders = new Headers();
    myheaders.append('user-key', apikey);
    myheaders.append('Accept', 'application/json');
    gametextarray = [];
    gameidarray = [];
    genresnodearray = [];
    keywordsnodearray = [];
    ratingnodearray = [];
    ttbnodearray = [];

    reset_data();

    idortext = 'text';

    /*var fe = fetch('https://cors-anywhere.herokuapp.com/https://api-endpoint.igdb.com/games/?search=Halo&fields=*', {headers: myheaders})
    .then(res2 => res2.json())
    .then(data => console.log("halo",data))
    .catch(function (err) {
        console.log(err.message);
        console.log(err.stack);
    })
    .catch(function (err) {
        console.log(err.message);
        console.log(err.stack);
    });*/


}

function checkForElement(jnode) {
    if (!document.getElementById('btnaddinfo')) {
        init();
        addButton();
        addAdditionalInfo();
    }
}

function populateGametextarray(element) {
    //get the game name
    //console.log(element);
    var gameurlelement = element.getElementsByTagName('a');
    var gameurl = gameurlelement[0].getAttribute("href");
    var found = gameurl.match(/[A-Za-z0-9_\-]*$/);
    var textfromurl = found[0].replace(/-/g, " ");
    var span1 = element.getElementsByTagName('span');
    var gametext = span1[0].innerHTML;
    var cururl = corsproxy + apiurl + req + gametext;
    gametextarray.push(gametext);
    //console.log(textfromurl);
    //gametextarray.push(cururl);
    //console.log(cururl)

}

function populateGameidarray(element) {
    // get sibling. sibling has game id
    var sib = element.nextSibling;
    var gameid = sib.getAttribute("data-game");
}

function addTextNodes(div) {
    for (var i = 0; i < div.length; i++) {
        //     console.log(div[i]);
        addTextNode(div[i], i);
    }
}

function addTextNode(el, i) {
    var genrestext = document.createTextNode('Genres: ');
    var genresspan = document.createElement('span');
    genresspan.style.fontSize = 'medium';
    genresspan.style.textDecoration = 'underline';
    genresspan.appendChild(genrestext);
    el.appendChild(genresspan);
    var genresnode = document.createElement('span');
    genresnode.setAttribute("id", "usgenre" + i.toString());
    el.appendChild(genresnode);
    genresnodearray.push(genresnode);
    el.appendChild(document.createTextNode('   |   '));
    var keywordstext = document.createTextNode('Keywords: ');
    var kwspan = document.createElement('span');
    kwspan.style.fontSize = 'medium';
    kwspan.style.textDecoration = "underline";
    kwspan.appendChild(keywordstext);
    el.appendChild(kwspan);
    var keywordsnode = document.createElement('span');
    keywordsnode.setAttribute("id", "uskw" + i.toString());
    el.appendChild(keywordsnode);
    keywordsnodearray.push(keywordsnode);
    el.appendChild(document.createTextNode('   |   '));
    var ratingtext = document.createTextNode('Rating: ');
    var rtspan = document.createElement('span');
    rtspan.style.fontSize = 'medium';
    rtspan.style.textDecoration = "underline";
    rtspan.appendChild(ratingtext);
    el.appendChild(rtspan);
    var ratingnode = document.createTextNode('');
    el.appendChild(ratingnode);
    ratingnodearray.push(ratingnode);
    el.appendChild(document.createTextNode('   |   '));
    var ttbtext = document.createTextNode('TTB: ');
    var ttbspan = document.createElement('span');
    ttbspan.style.fontSize = 'medium';
    ttbspan.style.textDecoration = 'underline';
    ttbspan.appendChild(ttbtext);
    var ttbnode = document.createElement('span');
    el.appendChild(ttbspan);
    el.appendChild(ttbnode);
    ttbnodearray.push(ttbnode);

    if (idortext == 'text') {
        populateGametextarray(el);
    }
    if (idortext == 'id') {
        populateGameidarray(el);
    }


}


function addButton() {
    //add Button
    var btn = document.createElement("button");
    var btntext = document.createTextNode('Load info (less hits)');
    btn.appendChild(btntext);
    btn.addEventListener("click", function () {
        load(idortext);
    }, false);
    btn.setAttribute("id", "btnaddinfo");
    var btn2 = document.createElement("button");
    var btntext2 = document.createTextNode('Load genres (more hits)');
    btn2.appendChild(btntext2);
    btn2.addEventListener("click", function () {
        loadgenres(idortext);
    }, false);
    btn2.setAttribute("id", "btnaddinfo2");
    var btn3 = document.createElement("button");
    var btntext3 = document.createTextNode('Load keywords (more hits)');
    btn3.appendChild(btntext3);
    btn3.addEventListener("click", function () {
        loadkws(idortext);
    }, false);
    btn3.setAttribute("id", "btnaddinfo3");
    var sidebar = document.getElementsByClassName("user-details-sidebar");
    if (sidebar) {
        var sibling = sidebar[0].nextSibling;

        sibling.parentNode.insertBefore(btn, sibling);
        sibling.parentNode.insertBefore(btn2, sibling);
        sibling.parentNode.insertBefore(btn3, sibling);
        sibling.parentNode.insertBefore(document.createElement("hr"), sibling);
    }

    function load(str) {
        loadbytext4(gametextarray, 1, 1, 0, 0, 0);
    }

    function loadgenres(str) {
        loadbytext4(gametextarray, 0, 0, 1, 0, 0);
    }

    function loadkws(str) {
        loadbytext4(gametextarray, 0, 0, 0, 1, 0);
    }

    //    var firstbtn = document.getElementsByClassName("panel-title");
    //    firstbtn[0].parentNode.insertBefore(document.createElement('hr'), firstbtn[0]);
    //    firstbtn[0].parentNode.insertBefore(btn, firstbtn[0]);
    //   firstbtn[0].parentNode.insertBefore(document.createElement('hr'), firstbtn[0]);
}

function addAdditionalInfo(jnode) {
    console.log('Adding button and placeholders for extra info');
    //	var pageDivs = document.getElementsByClassName("media-body");
    //	addTextNodes(pageDivs);

    var selector = "#content-page > div > div.content-left.col-md-push-2.col-md-10 > div:nth-child(2) > div.panel-body.listentries > div > div:nth-child(n) > div.media-body";
    var selector2 = "#content-page > div > div.content-left.col-md-push-2.col-md-10 > div > div.panel-body.listentries > div > div:nth-child(5) > div";
    var selectorgen = "div.listentries div.media div.media-body";
    var queryres = document.querySelectorAll(selectorgen);
    addTextNodes(queryres);
    // console.log(queryres);

}

function removeChildren(element) {
    while (element.firstChild) {
        element.removeChild(element.firstChild);
    }
}

async function loadbytext4(textarray, r = 0, ttb = 0, g = 0, k = 0, reset = 0) {
    console.log("Try loading data");
    if (reset != 0) {
        reset_data();
    } else {

        if (r != 0) {
            ratingnodearray.forEach(element => element.nodeValue = 'loading..');
        }
        if (g != 0) {
            genresnodearray.forEach(element => element.innerHTML = 'loading..');
        }
        if (k != 0) {
            keywordsnodearray.forEach(element => element.innerHTML = 'loading..');
        }
        if (ttb != 0) {
            //console.log(ttbnodearray);
            ttbnodearray.forEach(element => element.innerHTML = 'loading..');
        }
        if (r != 0 || g != 0 || k != 0) {
            console.log("games_text_info", Object.keys(games_text_info).length, ", gametextarray", gametextarray.length);
            if (Object.keys(games_text_info).length == gametextarray.length) {
                console.log("processing data cached");
                if (r != 0) {
                    gather_ratings();
                }
                if (g != 0) {
                    gather_genres();
                }
                if (k != 0) {
                    gather_kws();
                }
                if (ttb != 0) {
                    gather_ttb();
                }
            } else {
                reset_data();
                var gamespromise = gather_games_text_info(textarray);
                gamespromise
                    .then(function (result) {
                        console.log("processing data");
                        //console.log("gg",games_text_info);
                        if (r != 0) {
                            gather_ratings();
                        }
                        if (g != 0) {
                            gather_genres();
                        }
                        if (k != 0) {
                            gather_kws();
                        }
                        if (ttb != 0) {
                            gather_ttb();
                        }
                    })
                    .then(function () {
                        //console.log("2. ", "id_list", Object.keys(id_list).length, ", games_info", Object.keys(games_info).length, ", gametextarray", gametextarray.length);
                    })
                    .catch(function (err) {
                        console.log(err.message);
                        console.log(err.stack);
                    });
            }


        }

    }


}

function reset_data() {

    keyword_dict = {};
    genres_dict = {};
    id_list = {};
    genres_list_list = {};
    genres_list = {};
    keyword_list = {};
    games_info = {};
    games_text_info = {};

}

function gather_ttb() {
    console.log("gather ttb");
    ttbnodearray.forEach(function (element, index) {
        //var curr_id = id_list[gametextarray[index]];
        var curr_text = gametextarray[index];
        //console.log(curr_id);
        var game_info = games_text_info[curr_text];
        //console.log(game_info);
        removeChildren(element);
        if (game_info.time_to_beat) {
            var ttb = game_info.time_to_beat;
            var ttbstr = "";
            if (ttb.hastly) {
                ttbstr += +(ttb.hastly / 3600).toFixed(2) + "(h), ";
            }
            if (ttb.normally) {
                ttbstr += +(ttb.normally / 3600).toFixed(2) + "(n), ";
            }
            if (ttb.completely) {
                ttbstr += (ttb.completely / 3600).toFixed(2) + "(c)";
            }

            element.appendChild(document.createTextNode(ttbstr));
            //console.log("ttb", game_info.time_to_beat);
        } else {
            element.appendChild(document.createTextNode('n/a'));
        }
    });
}

function gather_ratings() {
    console.log("gather ratings");
    ratingnodearray.forEach(function (element, index) {
        //console.log("ratingelement",element, index);
        //var curr_id = id_list[gametextarray[index]];
        //var game_info = games_info[curr_id];

        var curr_text = gametextarray[index];
        var game_info = games_text_info[curr_text];
        //console.log("curr", curr_id, game_info);

        removeChildren(element);
        var ratingstr = '';
        ratingstr = ratingstr.concat('User: ');
        if (game_info.rating) {
            ratingstr = ratingstr.concat(Math.round(game_info.rating).toString());
            ratingstr = ratingstr.concat(' (' + game_info.rating_count.toString() + ')');
        } else {
            ratingstr = ratingstr.concat('/');
        }
        ratingstr = ratingstr.concat(', ');
        ratingstr = ratingstr.concat('Critics: ');
        if (game_info.aggregated_rating) {
            ratingstr = ratingstr.concat(Math.round(game_info.aggregated_rating).toString());
            ratingstr = ratingstr.concat(' (' + game_info.aggregated_rating_count.toString() + ')');
        } else {
            ratingstr = ratingstr.concat('/');
        }
        ratingstr = ratingstr.concat(', ');
        ratingstr = ratingstr.concat('Total: ');
        if (game_info.total_rating) {
            ratingstr = ratingstr.concat(Math.round(game_info.total_rating).toString());
            ratingstr = ratingstr.concat(' (' + game_info.total_rating_count.toString() + ')');
        } else {
            ratingstr = ratingstr.concat('/');
        }
        element.nodeValue = ratingstr;
    });
}

async function gather_genres() {
    var genresurl = corsproxy + apiurl + '/genres/' + Object.keys(genres_list).toString();
    //console.log("genres list", genres_list);
    console.log("genres url", genresurl);
    fetch(genresurl, {headers: myheaders})
        .then(res => res.json())
        .then(function (data) {
            //console.log("gg", genrenodesarray);
            console.log('genres', data);
            //console.log("games_info",games_info);
            //console.log("id_list",id_list);
            genresnodearray.forEach(function (element, index) {
                removeChildren(element);
                //console.log("index", index);
                //var curr_id = id_list[gametextarray[index]];
                var curr_text = gametextarray[index];
                //console.log("curr_id", curr_id);
                var game_info = games_text_info[curr_text];
                console.log("info",game_info);
                if(game_info.genres){
                  var curr_genre_ids = game_info.genres;
                  for (let genre_id of curr_genre_ids) {
                      //console.log("genre_id",genre_id);
                      var curr_genre = data.find(item => item.id == genre_id);
                      //console.log("curr_genre", curr_genre);
                      var newgenre = document.createElement('a');
                      newgenre.setAttribute("href", curr_genre.url);
                      newgenre.appendChild(document.createTextNode(curr_genre.name + ', '));
                      element.appendChild(newgenre);
                }
                }
                else {
                  element.appendChild(document.createTextNode("n/a"));
                }
                

            });
        })
        .catch(function (err) {
            console.log(err.message);
            console.log(err.stack);
        });
}

async function gather_kws() {
    var kwurl = corsproxy + apiurl + '/genres/' + Object.keys(genres_list).toString();
    //console.log("genres list", genres_list);
    console.log("kw", kwurl);
    fetch(kwurl, {headers: myheaders})
        .then(res => res.json())
        .then(function (data) {
            //console.log("gg", genrenodesarray);
            console.log('kws', data);
            //console.log("games_info",games_info);
            //console.log("id_list",id_list);
            keywordsnodearray.forEach(function (element, index) {
                removeChildren(element);
                //console.log("index", index);
                //var curr_id = id_list[gametextarray[index]];
                var curr_text = gametextarray[index];
                //console.log("curr_id", curr_id);
                var game_info = games_text_info[curr_text];
                if(game_info.keywords){
                  var curr_kw_ids = game_info.keywords;
                  for (let kw_id of curr_kw_ids) {
                    //console.log("genre_id",genre_id);
                    var curr_kw = data.find(item => item.id == kw_id);
                    //console.log("curr_genre", curr_genre);
                    var newkw = document.createElement('a');
                    newgenre.setAttribute("href", curr_kw.url);
                    newgenre.appendChild(document.createTextNode(curr_kw.name + ', '));
                    element.appendChild(newkw);
                  }
                }
                else {
                  element.appendChild(document.createTextNode("n/a"));
                }
                
            });
        })
        .catch(function (err) {
            console.log(err.message);
            console.log(err.stack);
        });
}

function gather_games_text_info(textarray) {
    var sliced = textarray;
    return Promise.all(sliced.map(text =>
        new Promise((resolve, reject) => {
            var info = load_info_by_text(text);
            info
                .then(data =>
                    new Promise((resolve2, reject2) => {

                        var curr_info = data[0];
                        //console.log("curr_info",curr_info);
                        games_text_info[text] = curr_info;
                        if (curr_info.genres) {
                            for (let genre of curr_info.genres) {
                                //console.log("genre_num",genre);
                                genres_list[genre] = genre;

                            }
                        }
                        if (curr_info.keywords) {
                            for (let kw of curr_info.keywords) {
                                keyword_list[kw] = kw;
                            }
                        }
                        resolve2("resolve2");
                    })
                        .catch(function (err) {
                            console.log(err.message);
                            console.log(err.stack);
                        })
                )
                .then(function () {
                    console.log("resolve", text)
                    resolve("resolve");
                })
                .catch(function (err) {
                    console.log(err.message);
                    console.log(err.stack);
                });
        })
    ))
        .catch(function (err) {
            console.log(err.message);
            console.log(err.stack);
        });


}


function gather_games_info(textarray) {


    //var sliced = textarray.slice(0,2);
    var sliced = textarray;
    console.log("Try loading for:", sliced);
    return Promise.all(sliced.map(text =>
        new Promise((resolve, reject) => {
            var curr_id = get_game_id_by_text(text);
            curr_id
                .then(id =>
                    new Promise((resolve2, reject2) => {
                        id_list[text] = id;
                        var info = load_info_by_id(id);
                        info.then(
                            function (result) {
                                return result;
                            }
                        )
                            .then(data =>
                                new Promise((resolve3, reject3) => {
                                    var curr_info = data[0];
                                    games_info[id] = curr_info;

                                    //console.log("curr_info",curr_info);
                                    if (curr_info.genres) {
                                        for (let genre of curr_info.genres) {
                                            //console.log("genre_num",genre);
                                            genres_list[genre] = genre;

                                        }
                                        //console.log("genres",curr_info.genres);
                                    }
                                    if (curr_info.keywords) {
                                        for (let kw of curr_info.keywords) {
                                            keyword_list[kw] = kw;
                                        }
                                    }
                                    //console.log("resolve3");
                                    resolve3("resolve3");
                                })
                            )
                            .then(function () {
                                //console.log("resolve2");
                                resolve2("resolve2");
                            })
                            .catch(function (err) {
                                console.log(err.message);
                                console.log(err.stack);
                            });

                    })
                )
                .then(function () {
                    //console.log("info", games_info);
                    console.log("resolve", text);

                    resolve("resolve");
                    //return true;
                })
                .catch(function (err) {
                    console.log(err.message);
                    console.log(err.stack);
                });
        })
    ))
        .then(function (data2) {
            console.log("glist", genres_list);
        })
        .catch(function (err) {
            console.log(err.message);
            console.log(err.stack);
        });
}

async function load_info_by_text(text) {
    var text_url = (corsproxy + apiurl + req + text + "&" + fieldsall);
    return fetch(text_url,
        {
            headers: myheaders
        }
    )
        .then(res => res.json())
        .then(function (data) {
            //console.log("data",data);
            return data;
        })
        .catch(function (err) {
            console.log(err.message);
            console.log(err.stack);
        });
}

async function load_info_by_id(id) {
    var id_url = (corsproxy + apiurl + '/games/' + id + fields);
    return fetch(id_url, {headers: myheaders})
        .then(res => res.json())
        .then(function (data) {
            return data;
        })
        .catch(function (err) {
            console.log(err.message);
            console.log(err.stack);
        });
}

function get_game_id_by_text(text) {
    return fetch(corsproxy + apiurl + req + text, {headers: myheaders})
        .then(res => res.json())
        .then(function (data) {
            var game_id_num = data[0].id;
            var game_id = game_id_num.toString();
            return game_id;
        })
        .catch(function (err) {
            console.log(err.message);
            console.log(err.stack);
        });
}

function load_single_by_text(text, index) {
    var result = fetch(corsproxy + apiurl + req + text, {headers: myheaders})
        .then(res => res.json())
        .then(function (data) {
            var game_id_num = data[0].id;
            id_list.push(game_id_num);
            console.log("pushed", game_id_num);
            var game_id = game_id_num.toString();
            load_single_by_id(game_id, index, 1, 0, 0);
            return data;
        });
}

/*--- waitForKeyElements():  A utility function, for Greasemonkey scripts,
    that detects and handles AJAXed content.

    Usage example:

        waitForKeyElements (
            "div.comments"
            , commentCallbackFunction
        );

        //--- Page-specific function to do what we want when the node is found.
        function commentCallbackFunction (jNode) {
            jNode.text ("This comment changed by waitForKeyElements().");
        }

    IMPORTANT: This function requires your script to have loaded jQuery.
*/
function waitForKeyElements(
    selectorTxt,
    /* Required: The jQuery selector string that
                                specifies the desired element(s).
               */
    actionFunction,
    /* Required: The code to run when elements are
                                found. It is passed a jNode to the matched
                                element.
               */
    bWaitOnce,
    /* Optional: If false, will continue to scan for
                                new elements even after the first match is
                                found.
               */
    iframeSelector
    /* Optional: If set, identifies the iframe to
                                search.
               */
) {
    var targetNodes, btargetsFound;

    if (typeof iframeSelector == "undefined")
        targetNodes = $(selectorTxt);
    else
        targetNodes = $(iframeSelector).contents()
            .find(selectorTxt);

    if (targetNodes && targetNodes.length > 0) {
        btargetsFound = true;
        /*--- Found target node(s).  Go through each and act if they
            are new.
   */
        targetNodes.each(function () {
            var jThis = $(this);
            var alreadyFound = jThis.data('alreadyFound') || false;

            if (!alreadyFound) {
                //--- Call the payload function.
                var cancelFound = actionFunction(jThis);
                if (cancelFound)
                    btargetsFound = false;
                else
                    jThis.data('alreadyFound', true);
            }
        });
    } else {
        btargetsFound = false;
    }

    //--- Get the timer-control variable for this selector.
    var controlObj = waitForKeyElements.controlObj || {};
    var controlKey = selectorTxt.replace(/[^\w]/g, "_");
    var timeControl = controlObj[controlKey];

    //--- Now set or clear the timer as appropriate.
    if (btargetsFound && bWaitOnce && timeControl) {
        //--- The only condition where we need to clear the timer.
        clearInterval(timeControl);
        delete controlObj[controlKey];
    } else {
        //--- Set a timer, if needed.
        if (!timeControl) {
            timeControl = setInterval(function () {
                    waitForKeyElements(selectorTxt,
                        actionFunction,
                        bWaitOnce,
                        iframeSelector
                    );
                },
                300
            );
            controlObj[controlKey] = timeControl;
        }
    }
    waitForKeyElements.controlObj = controlObj;
}