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.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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);
	}

})();