Pornolab Thumbnail Preview

View thumbnails of posts before/without clicking on them. Adds a post image preview to the torrent tables. No more wasting time checking each post page. Know what you want before you click.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         Pornolab Thumbnail Preview
// @namespace    hetisnietgay
// @version      0.3.4
// @description  View thumbnails of posts before/without clicking on them. Adds a post image preview to the torrent tables. No more wasting time checking each post page. Know what you want before you click.
// @author       hetisnietgay
// @match        *pornolab.net/forum/*
// @match        *rutracker.org/forum/*
// @grant        GM_xmlhttpRequest
// @connect      pornolab.net
// @license			 MIT

// ==/UserScript==

// Load the image before trying to obtain the dimensions
async function loadImage(url) {
	let img = new Image();
	img.src = url.replace(/\s/g, '');
	await img.decode();
	return img;
};
// Map image url array to preloaded images array
async function fetchImages(urls) {
	if (!urls)
		return;
	// Images load asynchronously so have to await all of the urls in the array
	let unresolved = urls.map(loadImage);
	let results = await Promise.allSettled(unresolved);

	let errors = results.filter(result => result.status === 'rejected')
		.map(result => result.value);

	if (errors.length)
		console.error(errors);

	results = results.filter(result => result.status === 'fulfilled')
		.map(result => result.value);

	if (!results.length)
		return;

	//console.log(results);
	return results;
}

function xmlGet(url) {
	return new Promise((resolve, reject) => {
			GM_xmlhttpRequest({
			method: "GET",
			url: url,
			onload: (res) => {
			resolve(res.responseText);
			},
			onerror: (err) => {
				reject(err);
			}
		});
	});
}

// Image URL for if thumbnail failed
const errorURL = "https://cdn.pixabay.com/photo/2017/02/12/21/29/false-2061131_960_720.png";

// Async delay
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

(async () => {
	'use strict';
	let table = document.querySelectorAll(".forumline.forum, .forumline.tablesorter")[0];

	let forumNameHeader, topicNameHeader;
	// Second child because fix for odd table
	let tableHead = table.querySelector("tr:first-child > th:nth-child(2)");
	let screenshotHead = Object.assign(document.createElement('th'), {
		className: `{sorter: false}`,
		innerHTML: `<b class="tbs-text">Screenshot</b>`,
	});

	let torrents = Array.from(table.querySelectorAll("tbody > tr"));
	let linkSelect = ".tLink";
	let tdSelect = "td:nth-child(2)";
	// Resize table columns and other page specific things, pretty messy rn
	if (table.id === "t-seed") {
		forumNameHeader = table.querySelector("th[data-resizable-column-id='forum_name']");
		topicNameHeader = table.querySelector("th[data-resizable-column-id='topic_name']");
		forumNameHeader.style = "width: 0.01%;";
		topicNameHeader.style = "width: 30%;";
		screenshotHead.setAttribute('data-resizable-column-id', 'preview_col');
		screenshotHead.style = "width: 30%;";
	} else if (table.id === "tor-tbl") {
		forumNameHeader = table.querySelector("th[width='25%']");
		topicNameHeader = table.querySelector("th[width='75%']");
		forumNameHeader.setAttribute("width", "0.01%");
		topicNameHeader.setAttribute("width", "30%");
		screenshotHead.setAttribute("width", "40%");
	} else if (table.id === "srch-tbl") {
		linkSelect = "a.topictitle";
		forumNameHeader = table.querySelector("col[width='25%']");
		topicNameHeader = table.querySelector("col[width='75%']");
		let screenshotCol = forumNameHeader.cloneNode();

		table.querySelector("colgroup > col:first-child").after(screenshotCol);
		forumNameHeader.setAttribute("width", "0.01%");
		topicNameHeader.setAttribute("width", "30%");
		screenshotCol.setAttribute("width", "40%")
	} else {
		linkSelect = "a.torTopic";
		let tdSelect = "td:first-child";
		topicNameHeader = table.querySelector("col[width='75%']");
		let screenshotCol = topicNameHeader.previousElementSibling;
		topicNameHeader.setAttribute("width", "30%");
		screenshotCol.setAttribute("width", "40%");

		// First few aren't torrent posts
		let torSelector = "td.topic_id > img.topic_icon:not([src='//static.pornolab.net/templates/default/images/folder_announce.gif'], [src='//static.pornolab.net/templates/default/images/folder_announce_new.gif'])";
		let firstTorrent = table.querySelector(torSelector).parentNode.parentNode;
		let index = Array.prototype.indexOf.call(firstTorrent.parentNode.children, firstTorrent);
		torrents = torrents.slice(index);

		// Fixes for odd table
		tableHead = table.querySelector("tr:first-child > th:first-child");
		let emptyHead = document.createElement("th");
		tableHead.before(emptyHead);

		table.querySelectorAll("th[colspan], .topic_id[colspan]").forEach(iconCell => iconCell.removeAttribute("colspan"));
		screenshotHead = Object.assign(document.createElement("th"), {
			innerHTML: "Screenshot",
		});
	}

	tableHead.before(screenshotHead);

	for (const torrent of torrents) {
		// Post link
		let link = torrent.querySelector(linkSelect);

		if (!link)
			continue;

		// Fetch post page
		let res = await xmlGet(link.href);
		let div = document.createElement("div");
		div.innerHTML = res;

		// Table cell for image(s)
		let imgCell = document.createElement("td");

		// Get first 2 post image URLs
		let postImgs = Array.from(div.querySelectorAll("var.postImg"))
										.slice(0, 2)
										.map((postImg) => postImg.title);

		// Fetch loaded images with URLS
		let posters = await fetchImages(postImgs);

		//let posters = postImgs.filter((img) => (img.naturalHeight > 350 || img.naturalWidth > 350) && (img.naturalWidth / img.naturalHeight < 4)); // extra check for banners
		if (!posters) {
			let img = await loadImage(errorURL);
			img.style = "height: 50px;"
			imgCell.append(img);
		} else {
			// If using imgbox, we can't tell whether it's a big or thumbnail image from url,
			// though the requirements for thumbnails are they have to be under 350px on each side.
			// Given that most 'poster' images are well over this, it *seems* pretty reliable (for now).
			posters = posters.filter((img) => (img.naturalHeight > 350 || img.naturalWidth > 350) && (img.naturalWidth / img.naturalHeight < 4));

			let width;
			if (posters && posters.length === 2) {
				width = [];
				let totalWidth = posters[0].naturalWidth + posters[1].naturalWidth;
				width[0] = (100 * (posters[0].naturalWidth / totalWidth));
				width[1] = (100 * (posters[1].naturalWidth / totalWidth));
			}

			posters.forEach((img, i) => {
				if (posters.length === 2) {
					img.style = "width: 100%; border: 5px;";
					img.style = "width: " + width[i] + "%; border: 5px;";
				} else {
					img.style = "width: 100%; border: 5px;";
				}
				// Add to container
				imgCell.append(img);
			});
		}

		// Add to table
		torrent.querySelector(tdSelect).before(imgCell);

		// Slow load images so we don't get IP blocked
		await sleep(400);
	}

})();