asensualmarquee / RST: Renovation Script for TSR

// ==UserScript==
// @name RST: Renovation Script for TSR
// @namespace asensualmarquee
// @description (NOTE: This script previously allowed the user to bypass the wait times when downloading files, but there was update to TSR that ensures such a workaround is probably impossible. And so, while the script's other features still work, that one doesn't. Please keep that in mind.) Fixes The Sims Resource's shitty behavior by streamlining things, removing cruft, and implementing more advanced behavior.
// @version 2.0.0
// @author asensualmarquee
// @copyright 2019, asensualmarquee (https://openuserjs.org/users/asensualmarquee)
// @license MIT
// @match http://thesimsresource.com/*
// @match https://thesimsresource.com/*
// @match http://www.thesimsresource.com/*
// @match https://www.thesimsresource.com/*
// @grant none
// @updateURL https://openuserjs.org/meta/asensualmarquee/RST_Renovation_Script_for_TSR.meta.js
// ==/UserScript==

// Changelog
// ---------
// 2.0.0
// - Removed code that augmented downloading functionality since TSR fixed the oversight permitted this in the first place.
// * Fixed incompatibilities with TSR's latest changes.
// * Changed the description to reflect reality but not imply the feeling of hyperbolic stupidity.
// 1.3.1
// * Description changed to reflect reality.
// -----
// 1.3.0
// + Added the ability to use filters without logging into the website.
// * Made the description in the script meta block more succinct.
// * Added punctuation to the script comments because why not.
// -----
// 1.2.1
// * Fixed script breaking when catalog has no page bar.
// * Corrected syntax and incorrect usage.
// -----
// 1.2.0
// + Added download link to gallery items' preview images.
// + Changed download links to point towards the file instead of the download page.
// * Fixed VIP banner being saved from destruction.
// * Fixed issue where the link to a gallery item's page isn't available when the item appears.
// -----
// 1.1.0
// + "Infinite" scrolling
// + Destruction of VIP banner
// + Death of bottom sticky
// + Added changelog in the source code
// -----
// 1.0.1 ~ 1.0.3
// * Changes to UserScript meta block
// -----
// 1.0.0
// * Initial release

(function() {
  const d = document;
  const catalog = {
      progress: 0,
      nextPageUrl: ""
  };
  
  // Returns a document fragment of a catalog page's items.
  // Includes a "separator" item at the top of the fragment.
  function getItemsFromCatalogPage(doc) {
    const itemsFrag = d.createDocumentFragment();
    const items = [...doc.querySelectorAll(".browse-file")];
    
    improveGalleryItems(items);
    
    items.forEach(function(i) {
      itemsFrag.appendChild(i);
    });    
    
    // Create a faux item that displays which page where the sebsequent items were retrieved,
    // and add it to the beginning.
    {
      const separator = itemsFrag.lastChild.cloneNode(true);
      separator.querySelector("img").remove();
      const currPageSplitUrl = doc.querySelectorAll(".on")[1].href.split("/");
      const currPageNum = currPageSplitUrl[currPageSplitUrl.indexOf("page") + 1];
      separator.querySelector(".browse-info").firstChild.textContent = "Page " + currPageNum;
      separator.querySelector(".browse-info").childNodes[1].remove();
      separator.querySelector(".browse-info").childNodes[2].remove();
      separator.style = "background-color: black";
      itemsFrag.insertBefore(separator, itemsFrag.firstChild);
    }
    
    return itemsFrag;
  }
  
  // Has an unfortunate side-effect of modifying "catalog". Redesign needed.
  function appendNextPageItemsToCatalog() {
    d.querySelector("#browse-files").appendChild(getItemsFromCatalogPage(this.response));
    catalog.nextPageUrl = this.response.querySelector(".icon-nextarrow").parentNode.href;
  }
  
  // Gets an item's DLL and navigates to it
  function getDll(itemId) {
    const xhr = new XMLHttpRequest();
    xhr.responseType = "json";
    xhr.open("GET", "/ajax.php?c=downloads&a=getdownloadurl&ajax=2&itemid=" + itemId);
    xhr.send();
    xhr.onload = function() {
      location.href = this.response.url;
    };
  }
  
  // "Improves" gallery items by doing the following:
  // 1. Banish the loading box.
  // 2. Divide each gallery item into two links: one to the item's page and one to the download.
  //    In addition, make it so that the link to the item's page is available on page load.
  // items: An array of HTML elements where each one has the ".browse-file" class.
  function improveGalleryItems(items) {
    items.forEach(function(i) {
      const id = i.attributes.itemid.value;
      
      // Perform step 1
      {
        i.removeChild(i.querySelector(".loadingbox"));
      }
      
      // Perform step 2
      {
        // Make the item's info a link the item page
        const info = i.querySelector(".browse-info");
        const itemLink = document.createElement("a");
        itemLink.href = "https://www.thesimsresource.com/downloads/" + id; // Set so that the item link is available from the beginning
        i.querySelector(".item-wrapper").appendChild(itemLink);
        itemLink.appendChild(info.cloneNode(true));
        info.remove(); 

        /*// Make the preview image a direct download link
        const dlLink = i.querySelector(".item-link");
        dlLink.classList.remove("item-link"); // Remove so that the href won't be overwritten
        dlLink.querySelector(".browse-border").style = "height: 219px";
        dlLink.href = "#download";
        dlLink.addEventListener("click", () => getDll(id));*/
      }
    });
  }
  
  // Set a barebones cookie to avoid any session issues.
  d.cookie = "tsrdlsess=;path=/";
  // Allow one to use filters and such without logging in.
  auth_isLoggedIn = function() {
    return true;
  }
  
  // The (non-paying) user is a very invaluable person.
  // So remove that annoying banner that begs you for money in exchange for supposed importance
  const nonSubBanner = d.querySelector("div.nonsubscriber");
  if (nonSubBanner !== null) {
    nonSubBanner.remove();
  }
  
  // Assuming we're on the catalog's page...
  if (d.querySelector("#browse-files"))
  {
    // Tear off that bottom sticky
    d.querySelector(".sticky_bottom").remove();
    
    // The % of how far we've scrolled through the catalog
    catalog.progress = 0;
    
    const nextPageBtn = d.querySelector(".icon-nextarrow");
    
    if (nextPageBtn) {
      catalog.nextPageUrl = d.querySelector(".icon-nextarrow").parentNode.href;

      // Implement "infinite" scroll
      {
        const xhr = new XMLHttpRequest();
        xhr.onload = appendNextPageItemsToCatalog;
        xhr.responseType = "document";

        window.addEventListener("scroll", function() {
          const catalogHeight = d.querySelector("#browse-files").clientHeight;
          catalog.progress += window.scrollY / catalogHeight;
          const threshold = 0.87;

          if (catalog.progress >= threshold) {
            catalog.progress = -1000; // Ensure that the next forward scroll doesn't cause a double load. Very crude
            xhr.open("GET", catalog.nextPageUrl);
            xhr.send();
          }

          else {
            catalog.progress = 0;
          }
        });
      }
    }
    
    improveGalleryItems([...d.querySelectorAll(".browse-file")]);
  }
  
  // Assuming we're on an item's page...
  if (d.querySelector(".details-wrapper")) {
    // Throw the loading boxes into an abandoned well along with the dead(?) bodies
    [...d.querySelectorAll(".loadingbox")].forEach((b) => b.remove());
    // Trash the animated spinner that's in front of the DL button
    d.querySelector("#okletsdothisspinner").remove();
    const dlBtn = d.getElementsByClassName("dl")[0];
    // Immediately unhide the download button
    dlBtn.style = "";
    /*// Delete the class(es) responsible for allowing the site's scripts to hook into the button and thus directing us into the anti-adblocker crap
    dlBtn.classList.remove("dl");
    // Directly link to the download
    dlBtn.href = "#download";
    dlBtn.addEventListener("click", () => getDll(dlBtn.attributes.itemid.value));*/
  }
})();