dropdabase / XHamster GX

// ==UserScript==
// @name        XHamster GX
// @description Plays all video thumbnails without a mouseover, also cleans up some elements.
// @version     0.2
// @author      dropdatabase (https://openuserjs.org/users/dropdabase)
// @license     MIT; https://opensource.org/licenses/MIT
// @namespace   https://openuserjs.org/users/dropdabase
// @match       https://xhamster.com/*
// @downloadURL https://openuserjs.org/install/dropdabase/XHamster_GX.user.js
// ==/UserScript==

(function () {
  'use strict';

  /*
  
  V0.2 ::
  - Convenient button to turn Playing Thumbnails [ON/OFF] - placed in various places depending on the page
  - Keyboard shortcut [A] will Toggle Playing Thumbnails
  - Tries not to load all the videos at once, has a 50ms delay between load request
  - Stores the toggle states on window.localstorage. So it remembers the state you have chosen.
  
  V0.1 ::
  - Plays all thumbnails without a mouseover
  - Removes the ViewCount and Thumbs Up on the videos, as I think it doesn't offer anything meaningful
  - Removes some LiveCam thumbnails that are on the videos thumbnails along the normal videos
  - Removes some Sponsored elements, like the whole TopBar or some banners that can appear
  - Removes all Live Video banner elements
  - Changes the padding of the thumbnails a to condense them on the y axis a bit.
  - Works in most pages (main, video page, user videos)
  - This is my first public script
  -------------------------------------------
  
   Notes ::
  - The placement of the "PLAYTHUMBS" button is not standard, and in some pages it will be missing
  - openuserjs changed my username from "dropdatabase" to "dropdabase" :-(

   Future ::
  - Video thumbnails that are loading will show as black boxex, I can fix this by making the video appear only when it is loaded
  - Overlay on thumbnails with some info like "watched before"?
  - Ability to hide videos?
  
  */

  const REMOVE_TOPVIDEOS = true; // Remove the top bar video section (promoted,recommended)
  const REMOVE_VIEWS = true; // Remove thumbsUp and viewcount
  const REMOVE_OTHER = true; // Hide Xhamster Live Banners
  const TIMECHECK = 90;
  const LOADDELAY = 40; // Load a video thumbnail every this time

  // ----------------
  var VIDS = null;
  var VC = null; // ARRAY Holds all the <div> video thumb elements
  var _ptop = "4px"; // Thumbs new top padding, it looks better this way
  var L = console.log; // Quicker Log

  var PG_DYN_PAGER = false; // Does the current page feature a dynamic pager system (video related)
  var xg_ANIMATE = true; // The global animate switch

  var btn;

  var pager = () => {
    this.timer = null;
    this.checktxt = "";
    this.el = null; // This is used to check if pager is active
  }

  var loadv = 0;

  // = ANIM BUTTON
  function c_button(name, onclick) {
    var el = document.createElement('a');
    el.innerHTML = name;
    el.className = 'xh-button';
    el.onclick = onclick;
    this.getEl = () => {
      return el;
    }
    this.set = (st) => {
      if (st === true) {
        el.style = "background:#494;color:#222;";
      }
      else {
        el.style = "";
      }
    }
  } // -----------

  // == START ::
  // :: Remove some sections 

  if (REMOVE_VIEWS) {
    removeAll('div.video-thumb-info div.video-thumb-info__metrics');
  }
  if (REMOVE_OTHER) {
    removeAll('div.wih-spb');
    removeAll('div.wih-spot-container');
    removeAll('div.clipstore-bottom');
  }
  if (REMOVE_TOPVIDEOS) {
    removeAll('div.top-video-block');
  }

  // :: Get all the video thumb elements
  VC = [];
  document.querySelectorAll('div.video-thumb').forEach((div) => {
    if (div.classList.contains('wih-cams-thumb-widget')) {
      L("> Removing Livecam Thumb");
      removeEl(div);
      return;
    }
    if (div.classList.contains('video-thumb--with-date')) { // Decorative element
      removeEl(div.children[0]);
      L("> Removed Date Added Strip");
    }
    div.style.paddingTop = _ptop; // Alter style
    VC.push(div);
  }); // -- end loop

  if (VC.length == 0) {
    L("> No Videos on this page");
    return;
  }

  // -- 
  if (VC[0].parentNode.classList.contains('thumb-list--related')) {
    L("> DYNAMIC PAGER PAGE");
    PG_DYN_PAGER = true;
  }

  // Read the saved settings
  xg_ANIMATE = window.localStorage.getItem("xg_ANIMATE");
  if (xg_ANIMATE == null) xg_ANIMATE = true;
  if (xg_ANIMATE == "true") xg_ANIMATE = true;
  else xg_ANIMATE = false;
  L("> Setting Load : xg_ANIMATE", xg_ANIMATE);

  // -- Setup Keyboard 
  document.onkeypress = function (evt) {
    evt = evt || window.event;
    var charCode = evt.keyCode || evt.which;
    var charStr = String.fromCharCode(charCode);
    if (charStr == "a") {
      L("[A] pressed, toggling Thumbnails");
      animate_toggle();
    }
  };

  createButton();
  btn.set(xg_ANIMATE);
  if (xg_ANIMATE) {
    start();
  }

  // === END MAIN =====================

  function createButton() {
    // -- UI Buttons 
    btn = new c_button("PlayThumbs", animate_toggle);

    // : Search elements to figure out where to put the button
    // Main page
    let P = document.querySelector('div.categories-container');
    if (P) return P.prepend(btn.getEl());

    // Video page
    P = document.querySelector('div.controls');
    if (P) return P.append(btn.getEl());

    // User video page
    P = document.querySelector('div.user-content-section');
    if (P) return P.prepend(btn.getEl());
  } // ---------------

  function start() {
    if (PG_DYN_PAGER) {
      setup_pager();
    }
    process();
  } // ---------

  // -- Prepare for loading
  function process() {
    if (VC == null) return;
    if (VIDS != null) {
      L("VIDS should be null");
      return;
    }
    VIDS = [];
    for (let div of VC) {
      let a = div.querySelector('a.video-thumb__image-container');
      if (!a) {
        L("[WARNING?] > Thumb Element did not have an <a> tag", div);
        continue;
      }

      // This is for the dynamic pager only
      if (pager.el == null) {
        pager.el = a;
        pager.checktxt = a.href;
      }

      // Some VR thumbnails don't have previews
      if (a.dataset.previewvideo == null) continue;

      VIDS.push({
        video: null,
        a: a,
        im: a.querySelector('img'),
        datav: a.dataset.previewvideo,
        datas: a.dataset.sprite,
        timer: -1
      });
    } // -- endloop

    loadv = 0;
    loadVids();
  } // -------------------------------------

  function loadVids() {
    // It is guaranteed for VIDS to not be empty
    // Also for v.previewvideo to be set
    let d = VIDS[loadv];
    let v = document.createElement('video');
    d.video = v;
    v.src = d.datav;
    v.autoplay = true;
    v.loop = true;
    d.a.dataset.previewvideo = "";
    d.a.dataset.sprite = "";
    if (d.im) d.im.style = "display:none";
    d.a.prepend(v);
    loadv++;
    if (loadv >= VIDS.length) {
      L("All Videos Loaded");
      return;
    }
    d.timer = setTimeout(loadVids, LOADDELAY);
  } // --

  // - Keyboard and Button callback
  function animate_toggle() {
    L("ANIMATE TOGGLE");
    L(xg_ANIMATE);
    xg_ANIMATE = !xg_ANIMATE;
    btn.set(xg_ANIMATE);
    window.localStorage.setItem('xg_ANIMATE', xg_ANIMATE);
    if (xg_ANIMATE) {
      start();
    }
    else {
      stop();
    }
    L(xg_ANIMATE);
  } // --------------------------------------

  function stop() {
    L("Stopping , Restoring Thumbnails");
    if (pager.timer != null) clearInterval(pager.timer); // Just in case it is running

    if (VIDS == null) return;
    for (let v of VIDS) {
      if (v.im) v.im.style = "display:visible"; // Just in case
      clearTimeout(v.timer); // Just in case
      if (!v.video) continue; // it has not loaded yet
      removeEl(v.video);
      v.a.dataset.previewvideo = v.datav;
      v.a.dataset.sprite = v.datas;
    }
    // bring image back
    VIDS = null;
  } // --------------------------------------

  // == Called when an arrow on the pager is clicked
  // - Remove the Video Elements
  // - Start a timer to poll for changes
  // - When new thumbnail infos loaded, create new thumbnails
  function pager_any_click() {
    if (xg_ANIMATE == false) return;
    // Remove old video elements
    if (VIDS != null) {
      for (let v of VIDS) {
        clearTimeout(v.timer); // just in case
        if (!v.video) continue; // it has not loaded yet
        removeEl(v.video);
      }
      VIDS = null;
    }

    // I need to know when the new thumbnails have loaded
    // So I am polling the HREF data of the first <a> to see when it changes
    if (pager.el == null) return; // Just in case
    if (pager.timer != null) return; // No need to set it again, the poll function is the same
    pager.timer = setInterval(() => {
      if (pager.el.href != pager.checktxt) { // New data:
        clearInterval(pager.timer);
        pager.timer = null;
        start();
      }
    }, TIMECHECK);
  } // -------------------------------------

  // - Do this BEFORE processing the videos
  // - Called on : (pagestart,pager refresh)
  function setup_pager() {
    let N = document.querySelectorAll('div.pager-container a');
    for (let n of N) n.onclick = pager_any_click;
    pager.el = null;
  } // -------------------------------------

  // =====================================
  // == HELPERS
  // =====================================
  function insertAfter(n, ref) {
    ref.parentNode.insertBefore(n, ref.nextSibling);
  } // -------------
  function removeEl(el) {
    el.parentNode.removeChild(el);
  } // -------------
  function removeAllIn(el) {
    while (el.firstChild) {
      el.removeChild(el.lastChild);
    }
  } // -------------
  function removeAll(n) {
    let elems = document.querySelectorAll(n);
    for (let e of elems) e.parentElement.removeChild(e);
  } // -------------

})();