Weinur2 / Gamebanana trashed files search

// ==UserScript==
// @namespace     https://openuserjs.org/users/Weinur2
// @name          Gamebanana trashed files search
// @description   https://gamebanana.com/search?mid=SearchResults&query=&game=*&section=*
// @copyright     2021, Weinur2 (https://openuserjs.org/users/Weinur2)
// @license       MIT
// @version       0.0.0
// @author        Weinur
// @match         https://gamebanana.com/search?mid=SearchResults&query=&game=*&vl[page]=*&section=*
// @require       https://code.jquery.com/jquery-3.5.1.min.js
// @run-at        document-start
// @grant         GM_setValue
// @grant         GM_getValue
// @grant         GM_listValues
// ==/UserScript==

var saveItem = "gwitems";
var loadString = "";
var currentItems = {};
var usedFastMethod = false;

function toNumber(number, float = false)
{
    var typeOfNumber = typeof number;
    switch(typeOfNumber)
    {
        case "number": return number;
        break;

        case "string": return float ? parseFloat(number) : parseInt(number, 10);
        break;

        case "default": return -1;
        break;
    }
    return -1;
}

function toInt(number)
{
    return Math.round(toNumber(number));
}

function toFloat(number)
{
    return toNumber(number, true);
}

function log(message, varName, debug)
{
    if(varName)
    {
        message = varName + ": " + message;
    }
    if(debug) {
        alert(message);
    } else {
        console.log(message);
    }
}

function copyToClipboard (text) {
  if (navigator.clipboard) { // default: modern asynchronous API
    return navigator.clipboard.writeText(text);
  } else if (window.clipboardData && window.clipboardData.setData) {     // for IE11
    window.clipboardData.setData('Text', text);
    return Promise.resolve();
  } else {
    // workaround: create dummy input
    var input = $('<input type="text" value="test">');
    input.val(text);
    document.body.append(input.get(0));
    input.focus();
    input.select();
    document.execCommand('copy');
    input.remove();
    return Promise.resolve();
  }
}

function startLoading(start = true)
{
    $("body, html").toggleClass("loading", start);
}

function startProgress(start = true)
{
    $("body, html").toggleClass("progress", start);
}

function getPercentValueFloat(loaded, total)
{
    return loaded / total * 100;
}

function getPercentValue(loaded, total)
{
    return Math.round(getPercentValueFloat(loaded, total));
}

function setProgressBarValue(value, start = false)
{
    if(start) startProgress();
    $(".progressSpinner").css("--progress-percent", value);
}

function getCurrentProgressBarValue()
{
    return toInt($(".progressSpinner").css("--progress-percent"));
}

function addProgressBarValue(value, stop = false, stopTimeout = 0)
{
    var currentPercent = getCurrentProgressBarValue();
    var nextPercent = Math.round(currentPercent + value);
    nextPercent = nextPercent >= 100 ? 100 : nextPercent;
    setProgressBarValue(nextPercent);
    if(stop && nextPercent == 100) {
       setTimeout(function() {
          startProgress(false);
       }, stopTimeout);
    }
}

function setProgressBarValueFloat(value)
{
    $(".progressSpinner").css("--progress-percent-float", value);
}


function getCurrentProgressBarValueFloat()
{
    return toFloat($(".progressSpinner").css("--progress-percent-float"));
}

function addProgressBarValueFloat(value, stop = false, stopTimeout = 0)
{
    var currentPercent = getCurrentProgressBarValueFloat();
    var nextPercent = currentPercent + value;
    nextPercent = nextPercent >= 100 ? 100 : nextPercent;
    var nextPercentRound = Math.round(nextPercent);
    setProgressBarValueFloat(nextPercent);
    setProgressBarValue(nextPercentRound);
    if(stop && nextPercentRound == 100) {
       setTimeout(function() {
          startProgress(false);
          setProgressBarValue(0);
          setProgressBarValueFloat(0);
       }, stopTimeout);
    }
}


function deleteMe(percent, itemType, id)
{
   var record = $(this).closest("record");
   if(!record.length) {
      var ele = "a.we" + itemType + "Id_" + id;
      record = $(ele).closest("record");
   }
   if(record.length) {
     record.remove();
   }
   addProgressBarValueFloat(percent, true, 1000);
   log("buja");
}

function save(itemType, id, set)
{
    var item = itemType + "_" + id;
    currentItems[item] = set;
    GM_setValue(item, set);
}

function saveItems()
{
    var newItems = {};
    var items = currentItems;
    for (let key of Object.keys(items)) {
        let value = currentItems[key];
        if(value !== undefined) newItems[key] = value;
    }
    for (let key of GM_listValues()) {
        let value = GM_getValue(key);
        if(value !== undefined) newItems[key] = value;
    }
    currentItems = newItems;
    var saveString = btoa(JSON.stringify(newItems));
    localStorage.setItem(saveItem, saveString);
    return saveString;
}

function load()
{
   if(loadString !== "")
   {
       currentItems = JSON.parse(atob(loadString));
   }
   var saveString = saveItems();
   console.log("saveThis (to clipboard):");
   console.log(saveString);
   copyToClipboard(saveString);
}
//load();

function getCurrentItem(itemType, id)
{
    return currentItems[itemType + "_" + id];
}

function fetchURLEx(url, callbackTrue, callbackFalse, callbackError) {
    const myInit = {
        method: 'GET',
        headers: {
            'pragma': 'no-cache',
            'cache-control': 'no-cache',
            'sec-ch-ua': '"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"',
            'origin': 'https://gamebanana.com',
            'sec-ch-ua-mobile': '?0',
            'sec-fetch-site': 'cross-site',
            'sec-fetch-mode': 'cors',
            'sec-fetch-dest': 'font',
            'referer': 'https://fonts.googleapis.com/',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
            'accept': '*/*',
            'x-client-data': 'CIe2yQEIorbJAQjEtskBCKmdygEI0qDKAQj4x8oBCLKaywEI5JzLAQioncsB',
            'accept-language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
            'accept-encoding': 'gzip, deflate, br',
        }
    };

    let myRequest = new Request(url, myInit);
    fetch(myRequest).then(function(response) {
        if(!response.ok)
        {
            throw Error(response.statusText);
        }
        return response.text();
    }).then(function(text) {
        var trashed = text.indexOf("TrashNoticeModule") >= 0;
        var private = text.indexOf("PrivateAccessNoticeModule") >= 0;
        if(trashed || private) {
            callbackTrue(trashed, private);
        } else {
            callbackFalse();
        }
    }).catch(function(error) {
        callbackError(error);
    });
}

function callbackFileDownload(percent, itemType, id, itemTypes, trashed, private)
{
    $(this).data("itemType", itemType)
           .data("id", id)
           .data("itemTypes", itemTypes)
           .toggleClass("fileDownload", true)
           .toggleClass("trashedFile", trashed)
           .toggleClass("privateFile", private);
    addProgressBarValueFloat(percent, true, 1000);
}

function downloadURLEx(url, fileName, mirror)
{
    setProgressBarValue(0, true);
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'blob';

    xhr.onprogress = function(pe) {
        console.log('progress');
        if (pe.lengthComputable) {
            var percent = getPercentValue(pe.loaded, pe.total);
            setProgressBarValue(percent >= 99 ? 99 : percent);
        }
    };

    xhr.onload = function(e) {
        if (this.status == 200) {
            addProgressBarValue(100, true, 1000);
            var blob = this.response;
            //TODO fallback needed for IE8 & IE9
            if (navigator.appVersion.toString().indexOf('.NET') > 0) {
                //IE 10+
                window.navigator.msSaveBlob(blob, fileName);
            } else {
                //Firefox, Chrome
                var a = document.createElement("a");
                var blobUrl = window.URL.createObjectURL(new Blob([blob], {type: blob.type}));
                document.body.appendChild(a);
                a.style = "display: none";
                a.href = blobUrl;
                a.download = fileName;
                a.click();
                $(a).remove();
            }
        } else if(mirror) {
            downloadURLEx(mirror, fileName);
        } else {
            addProgressBarValue(100, true, 1000);
        }
    };

    xhr.send();
}


function callbackFiles(itemType, id, item, itemTypes)
{
     $(Object.keys(item)).each(function() {
         var downloadURL = item[this];
         var downloadURL_ = "https://files.gamebanana.com/" + itemTypes + "/" + this;
         downloadURLEx(downloadURL_, this, itemTypes, downloadURL);
     });
}

function downloadFiles(itemType, id, itemTypes)
{
    var item = getCurrentItem(itemType + "_files", id);
    if(item !== undefined) {
        if(item) callbackFiles(itemType, id, item, itemTypes);
        return;
    }
    $.getJSON("https://api.gamebanana.com/Core/Item/Data", {
        itemtype: itemType,
        fields: "Files().aFiles()",
        itemid: id,
    }, function(response) {
        var downloadLinks = {};
        $(response).each(function(i, itemI) {
            $(Object.keys(this)).each(function(j, itemJ) {
               var that = itemI[itemJ];
               downloadLinks[that._sFile] = that._sDownloadUrl;
            });
        });
        item = downloadLinks;
        save(itemType + "_files", id, item);
        callbackFiles(itemType, id, item, itemTypes);
    }).done(function() { log('getJSON request succeeded!'); })
      .fail(function(jqXHR, textStatus, errorThrown) { log('getJSON request failed! ' + textStatus); usedFastMethod = false; })
      .always(function() { log('getJSON request ended!');});
}

function isBan(percent, itemType, id, callback, callbackDownload, itemTypes)
{
    var item = getCurrentItem(itemType, id);
    if(item !== undefined) {
        if(!item) {
            callback(percent, itemType, id);
        } else {
            callbackDownload(percent, itemType, id, itemTypes ? itemTypes : itemType.toLowerCase() + "s");
        }
        return;
    }
    if(itemTypes) {
       fetchURLEx("/" + itemTypes + "/" + id, function(trashed, private) {
          save(itemType, id, true);
          callbackDownload(percent, itemType, id, itemTypes, trashed, private);
       }, function() {
          save(itemType, id, false);
          callback(percent, itemType, id);
       }, function(error) {
          addProgressBarValueFloat(percent, true, 1000);
       });
       return;
    }
    $.getJSON("https://api.gamebanana.com/Core/Item/Data", {
        itemtype: itemType,
        fields: "Trash().bIsTrashed()",
        itemid: id,
    }, function(response) {
        log(response, "response");
        var string = response.toString().toLowerCase();
        var good = string.indexOf("true") >= 0;
        save(itemType, id, good);
        if(!good) {
            callback(percent, itemType, id);
        } else {
            callbackDownload(percent, itemType, id, itemTypes ? itemTypes : itemType.toLowerCase() + "s", true, false);
        }
    }).done(function() { log('getJSON request succeeded!'); })
      .fail(function(jqXHR, textStatus, errorThrown) { log('getJSON request failed! ' + textStatus); usedFastMethod = false; addProgressBarValueFloat(percent, true, 1000); })
      .always(function() { log('getJSON request ended!');});
}

function getButtonHtml(id, title)
{
    var html = `<div style="float: left; margin-left: 1em;" id="${id}Div"><button type="button" class="ExtendedContentButton" id="${id}Button"><spriteicon class="MiscIcon ShowContentIcon"></spriteicon><span>${title}</span></button></div>`;
    return html;
}

function createButtons(cssStyles) {
   $( ".InfiniteScroller" ).css(cssStyles).after( getButtonHtml("trashedFilesOnly", "Show trashed/private files only!") )
                                           .after( getButtonHtml("autoScroll", "Show all elements!") );
   $( "#trashedFilesOnlyDiv, #autoScrollDiv" ).css(cssStyles).css("margin-left", "1em");
}

function handler(cssStyles)
{
    if( $( "#trashedFilesOnlyDiv" ).length ) return;
    createButtons(cssStyles);
    $(window).on("scroll", function() {
        if( ! $( "#trashedFilesOnlyDiv" ).length ) createButtons(cssStyles);
        var element = $( ".InfiniteScroller" );
        if(element.length) {
            element.css(cssStyles);
        } else {
            $( "#autoScrollDiv" ).remove();
            $( "#trashedFilesOnlyDiv[style]" ).removeAttr("style");
        }
    });
}

function infiScroller() {
   var infi = $(".InfiniteScroller");
   if(infi.length) {
      infi.get(0).scrollIntoView();
      infi.find("button").trigger("click");
   } else {
      $("body, html").toggleClass("loading", false);
      var element = $( "#trashedFilesOnlyDiv" );
      if(element.length) element.get(0).scrollIntoView();
      return;
   }
   window.setTimeout(infiScroller, 500);
}

function getCss() {
    var style = document.createElement("style");
    style.innerHTML = `
       html.loading,
       html.progress {
            overflow-y: hidden;
            pointer-events: none;
       }

       body.not(.loading) .loadingspinner,
       body:not(.progress) .progressSpinner {
            display: none;
       }

       body.loading,
       body.progress {
            height: 100vh;
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            width: 100%;
            overflow-y: scroll;
            display: block;
            pointer-events: none;
       }

       body.loading:after,
       body.progress:after {
            display: block;
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: grey;
            pointer-events: none;
            z-index: 999999999999999;
            opacity: 0.5;
       }

       body.loading .loadingspinner {
            pointer-events: none;
            width: 200px;
            height: 200px;
            border: 20px solid transparent;
            border-color: #eee;
            border-top-color: #3E67EC;
            border-radius: 100%;
            animation: loadingspin 1s linear infinite;
            z-index: 10000000000000000000;
            left: calc(50% - 100px);
            top: calc(50% - 100px);
            position: fixed;
            box-sizing: border-box;
       }

       body.progress .progressSpinner {
            pointer-events: none;
            width: 250px;
            height: 250px;
            z-index: 10000000000000000000;
            left: calc(50% - 125px);
            top: calc(50% - 125px);
            position: fixed;
            box-sizing: border-box;
            --progress-percent: 0;
            --progress-percent-float: 0;
            display: grid;
	        place-content: center;
	        transform: scale(1);
	        clip-path: circle(49%);
	        color: #ff4d4d;
            width: 250px;
            height: 250px;
       }

       body.progress .progressSpinner:before,
       body.progress .progressSpinner:after {
	        grid-row: 1;
	        grid-column: 1;
            font-size: 16px;
       }

       body.progress .progressSpinner:before {
	        border: solid 2em transparent;
	        padding: 6em;
	        border-radius: 50%;
	        box-shadow: inset 0 0 0 0.5em currentcolor;
	        --progress-slice: calc(360deg/16);
	        --progress-s-gap: calc(0.2*var(--progress-slice));
	        --progress-solid: calc((1 - 0.2)*var(--progress-slice));
	        background: repeating-conic-gradient(from calc(.5*var(--progress-s-gap)), currentcolor 0% var(--progress-solid), transparent 0% var(--progress-slice)) border-box;
	        filter: blur(0.5px);
	        --progress-mask: conic-gradient(red 0% calc(var(--progress-percent)*1%), rgba(255, 0, 0, .3) 0%), linear-gradient(red, red) border-box, radial-gradient(red 0% 5.5em, transparent calc(5.5em + 1px) calc(5.75em - 1px), red 5.75em calc(6em + 1px), transparent calc(6em + 2px));
	        -webkit-mask: var(--progress-mask);
	        -webkit-mask-composite: source-in, source-out;
            mask: var(--progress-mask);
            mask-composite: exclude;
	        content: '';
       }

       body.progress .progressSpinner:after {
	        place-self: center;
	        counter-reset: percent var(--progress-percent);
	        font: 3.75em consolas, monaco, ubuntu mono, monospace;
	        content: counter(percent) '%';
       }

       @keyframes progress {
	       90%, 100% {
		      --progress-percent: 100;
	       }
       }

       @keyframes loadingspin {
	       100% {
			 transform: rotate(360deg)
	       }
       }

       a.Name.fileDownload {
           position: relative;
           display: block;
       }

       a.Name.fileDownload:after {
           display: block;
           position: absolute;
           top: 0;
           right: 0;
           bottom: 0;
           content: "Download";
           opacity: 0.8;
           border-radius: 3px;
           font-size: var(--ButtonFontSize);
           font-family: var(--ButtonFontFamily);
           outline: none;
           background: var(--ButtonBackground);
           color: var(--ButtonFontColor);
       }

       a.Name.fileDownload:hover:after {
           opacity: 1.0;
       }

       a.Name.fileDownload.trashedFile:after {
           content: "Download (trashed)";
       }

       a.Name.fileDownload.privateFile:after {
           content: "Download (private)";
       }

       a.Name.fileDownload.trashedFile.privateFile:after {
           content: "Download (trashed / private)";
       }
   `;
   return $(style);
}

function getLoading()
{
   return $('<div class="loadingspinner"></div>');
}

function getProgressBar()
{
   return $('<div class="progressSpinner"></div>');
}

function getURLParams()
{
    var params = $(location).attr("search").split("&");
    var newParams = {};
    $(params).each(function(i) {
        var paramKeyValue = this.split("=");
        newParams[paramKeyValue[0]] = paramKeyValue[1];
    });
    return newParams;
}

$(document).ready(function() {
    var urlParams = getURLParams();
    var section = urlParams["section"];
    var sections = section + "s";
    sections = sections.toLowerCase();
    $("body").data("gwsection", section).data("gwsections", sections);
    getCss().appendTo("body");
    getLoading().appendTo("body");
    getProgressBar().appendTo("body");
    var cssStyles = {
                 "margin-top": "2em",
                 "margin-bottom": "2em",
                 "float": "left"
              };
    var buttonCss = {
        "background": "#1c272e",
        "color": "var(--DefaultBlueColor)"
    };
    handler(cssStyles);
    $("body").on("click", ".Controls a[title]", function() {
          $(".InfiniteScroller").triggerHandler("check");
    });
    $("body").on("mouseover touchmove touchend touchcancel check", ".InfiniteScroller", function(e) {
        handler(cssStyles);
    });
    $("body").on("click", "#autoScrollDiv", function() {
       $("body, html").toggleClass("loading", true);
       infiScroller();
    });
    $("body").on("click", "#trashedFilesOnlyDiv", function() {
        setProgressBarValueFloat(0);
        setProgressBarValue(0, true);
        var selector = 'records.List recordcell a.Name[href^="https://gamebanana.com/' + sections + '/"]';
        var elements = $(selector);
        var length = elements.length;
        var percent = getPercentValueFloat(1, length);
        elements.each(function(i) {
            console.log("Element: " + i);
            var that = this;
            var id = $(that).attr("href").split(sections + "/");
            id = parseInt(id[1], 10);
            $(that).toggleClass("we" + section + "Id_" + id, true);
            setTimeout(function() {
                isBan(percent, section, id, deleteMe.bind(that), callbackFileDownload.bind(that), usedFastMethod ? false : sections);
            }, usedFastMethod ? 3600 : 0);
        });
        var button = $("#trashedFilesOnlyButton").css(buttonCss);
        setTimeout(function() { button.filter("[style]").removeAttr("style"); }, 700);
        return false;
    });
    $("body").on("click", "a.Name.fileDownload", function(event) {
          var element = $(this);
          event.preventDefault();
          setProgressBarValue(0, true);
          downloadFiles(element.data("itemType"), element.data("id"), element.data("itemTypes"));
    });
});