GBT scroller-coaster (GBTSC)

[BETA] Infinity scrolling for videos (main, search, channels) & photos (photos, search, channels)

As of 2024-11-29. See the latest version.

// ==UserScript==
// @name         GBT scroller-coaster (GBTSC)
// @namespace    _pc
// @version      0.9
// @license      MIT
// @description  [BETA] Infinity scrolling for videos (main, search, channels) & photos (photos, search, channels)
// @author       verydelight
// @match        *://*.gayboystube.com/*
// @icon         https://www.gayboystube.com/favicon.ico
// @run-at       document-end
// @grant        GM.xmlHttpRequest
// @grant        GM_download
// @grant        GM_xmlHttpRequest
// @grant        GM.download
// ==/UserScript==
'use strict';
if(
(window.location.pathname=="") ||
(window.location.pathname=="/") ||
(window.location.pathname.startsWith("/channels")) ||
(window.location.pathname.startsWith("/search/videos")) ||
(window.location.pathname.startsWith("/photos")) ||
(window.location.pathname.startsWith("/search/photos"))
){
	let area = window.location.pathname.split("/");
	let testArea,baseUrl
    let videoPage=false;
	const identifier = $('div[id]')[2].id;
	testArea = area[2] ? `${area[1]}/${area[2]}` : (area[1] || "videos");
	switch(testArea) {
		case "videos":
		baseUrl = "https://www.gayboystube.com/page";
		gbtScroller(identifier,baseUrl);
		videoPage = true;
		break;
		case "search/videos":
		baseUrl = "https://www.gayboystube.com/search/videos/"+area[area.length - 2]+"/page";
		gbtScroller(identifier,baseUrl);
		videoPage = true;
		break;
		case testArea.match(/^channels\/[0-9]{1,3}/)?.input:
		baseUrl = "https://www.gayboystube.com/channels/"+area[area.length - 3]+"/"+area[area.length - 2]+"/page";
		gbtScroller(identifier,baseUrl);
		videoPage = true;
		break;
		case "photos":
		baseUrl = "https://www.gayboystube.com/photos/page";
		gbtScroller(identifier,baseUrl);
		break;
		case "search/photos":
		baseUrl = "https://www.gayboystube.com/search/photos/"+area[area.length - 2]+"/page";
		gbtScroller(identifier,baseUrl);
		break;
		case "photos/channels":
		baseUrl = "https://www.gayboystube.com/photos/channels/"+area[area.length - 2]+"/page";
		gbtScroller(identifier,baseUrl);
		break;
		default:
	}
	function gbtScroller(identifier,baseUrl){
		const style = document.createElement('style')
		style.type = 'text/css';
		style.textContent = `
		.gbtloader {
			/*color: #ffffff;*/
			width: fit-content;
			font-weight: bold;
			font-family: monospace;
			font-size: 30px;
			background:linear-gradient(90deg,#000 50%,#0000 0) right/200% 100%;
			animation: l21 1.5s infinite linear;
		}
		.gbtloader::before {
			content :"Loading...";
			color: #0000;
			padding: 0 5px;
			background: inherit;
			background-image: linear-gradient(90deg,#fff 50%,#000 0);
			-webkit-background-clip:text;
			background-clip:text;
		}
		@keyframes l21{
			100%{background-position: left}
		}
		`;
		document.head.appendChild(style);
		let videoListing = document.getElementById(identifier);
		videoListing.nextElementSibling.remove();
		let loadTrigger = document.createElement("postloader");
		loadTrigger.id = 'postloader-1';
		loadTrigger.classList.add('item','item-col','gbtloader');
		//loadTrigger.append("(Loading...)");
		videoListing.append(loadTrigger)
		let target = videoListing.getElementsByTagName('postloader')[0];
		let options = {
			root: null,
			rootMargin: '0px',
			threshold: 0
		}
		let firstCallImminent = true;
		let observer = new IntersectionObserver(callback, options);
		if (target){ observer.observe(target); }
		function callback() {
			if(!firstCallImminent){
				observer.unobserve(target);
				var nextPage = parseInt(loadTrigger.id.split("-")[1])+1;
				var nextPageUrl = baseUrl+nextPage+".html";
				loadNextPage(nextPageUrl,nextPage);
			}else{
				firstCallImminent = false;
			}
		}
		async function loadNextPage(url,nextPage) {
			try {
				const response = await GM.xmlHttpRequest({
					method: 'GET',
					responseType: 'document',
					url: url,
				});
				const newResults = response.response;
				var childResults = newResults.getElementById(identifier).children;
				while (childResults.length > 0)
				{



					//video case
					if (videoPage){
						if(childResults[0].querySelector('img')){
							const firstChildNode = childResults[0].firstElementChild;
							const lastChildNode = childResults[0].lastElementChild;
							const newWrapper = document.createElement("div");
							newWrapper.classList.add("item", "item-col");
							const aImg = childResults[0].querySelector('a');
							aImg.removeAttribute('class');
							aImg.classList.add("image");
							const videoImage = firstChildNode.querySelector('img');
							const videoElement = document.createElement("video");
							videoElement.alt = videoImage.getAttribute("alt");
							videoElement.poster = videoImage.getAttribute("data-src");
							videoElement.controls = false;
							videoElement.style.objectFit = "cover";
							videoElement.preload = "none";
							videoElement.height = videoImage.getAttribute("height");
							videoElement.width = videoImage.getAttribute("width");
							videoElement.crossOrigin = "use-credentials";
							videoElement.addEventListener('mouseenter', () => {
								if(!videoElement.src){
									const previewUrl = videoImage.getAttribute("data-preview")
									downloadPreview(previewUrl);
								}
								async function downloadPreview(url) {
									try {
										const response = await GM.xmlHttpRequest({
											method: 'GET',
											responseType: 'blob',
											url: url,
											headers: {
												"Content-Type": "video/mp4", "Accept": "video/mp4"
											},
										});
										const blob = new Blob([response.response],{type: 'video/mp4'});
										videoElement.src = URL.createObjectURL(blob);
									} catch (err) {
										console.error("GBTSC: Error in fetching and downloading preview file:", err);
									}
								}
								videoElement.play();
							});
							videoElement.addEventListener('mouseleave', () => {
								videoElement.load();
							});
							videoImage.replaceWith(videoElement);
							newWrapper.append(firstChildNode);
							newWrapper.append(lastChildNode);
							videoListing.appendChild(newWrapper);
						}else{
							childResults[0].remove()
						}
						//end of video case
					}else{
						//photo case
						videoListing.appendChild(childResults[0]);
					}
				}
				firstCallImminent = true;
				loadTrigger.remove();
				loadTrigger.id = 'postloader-'+nextPage;
				videoListing.append(loadTrigger);
				observer.observe(target);
			} catch (err) {
				console.error("GBTSC: Error in fetching and downloading next videos:", err);
				observer.unobserve(target);
				loadTrigger.remove();
			}
		}
	}
}