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.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 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);
	}

})();