NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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);
} // -------------
})();