Automatically unfolds spoilers and replaces thumbnails with full-sized images constrained to the viewport height while blocking thumbnails linking to adware.
< Feedback on PornoLab.net Thumbnail ExpanderEX
same for fastpic images on url: /forum/viewtopic.php?t=2073585 they were already fullsize, so why touch em??
same for imgbox thumbs here, they just disappear /forum/viewtopic.php?t=2821140
edit: - ignore previous posts here, here's a summary and analysis, I have found 3 different problems with the script:
1) - it's pic4all which disappear on the 1st link now and imagevnue ones are just left as small thumbs: /forum/viewtopic.php?t=1777687 problem: "blacklisted" hosts and their thumbs just disappear - that shouldn't happen
2) /forum/viewtopic.php?t=2821140 - here there are a lot of photos then video screens, but to see video screens you have to wait almost forever for the photos to load first
3) the problem that the script touches already fullsized images is there as well - it makes them disappear and wait for the very long queue to reappear again, why? : /forum/viewtopic.php?t=2073585
Although I wouldn't bother myself with proof i appreciate your valuable attention. But must inform you that it was uploaded from my alt account, which I currently have no access to because I made the mistake of registering it with a temporary email and logged out without ensuring I could log back in. Didn't tried to recover it either cause i have zero legitimate proof that it's mine besides it was last logged in quite a while ago and has only this script which is solely purpose for it's existing. Now i just decide to not care about it associating with me.
So despite i think that cooler name is impossible for such masterpiece someone must republish it under his name and sadly with different title too.
Which partially fixes your issues. I wish i could do fold but not today greasyfork not today.
// ==UserScript==
// @name PornoLab.net Thumbnail ExpanderEX
// @namespace http://pornolab.net/
// @version 1.1.1
// @description Automatically unfolds spoilers and replaces thumbnails with full-sized images constrained to the viewport height while blocking thumbnails linking to adware.
// @author Anonymous
// @license GPL-3.0-or-later
// @include http://pornolab.net/forum/viewtopic.php*
// @include https://pornolab.net/forum/viewtopic.php*
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
const autoUnfold = true;
const autoPreload = true;
const maxImgWidth = '80vw';
const maxImgHeight = '100vh';
const blockedHosts = ['piccash.net', 'picclick.ru', 'pic4cash.ru', 'picspeed.ru', 'picforall.ru', 'freescreens.ru'];
const enableImageReplacementInsideFoldsWithTitleContains = ['скрытый текст', 'Коврики', 'Примеры фото', 'Скринлист', 'Mb', 'Gb', 'Примеры', 'Скриншоты'];
function sleep(ms) {
return new Promise(res => setTimeout(res, ms));
}
class Ticker {
#last = new Date();
tick() { this.#last = new Date(); }
async wrap(cb, ms) {
this.tick();
await cb();
await this.after(ms);
}
async after(ms) {
const passed = new Date() - this.#last;
if (passed > ms) { return; }
await sleep(ms - passed);
}
}
const qhandler = new class {
#running = false
async run(queue) {
if (this.#running) { return; }
const ticker = new Ticker();
this.#running = true
for (let i = 0; i < queue.length; i++) {
while (!document.hasFocus()) { await sleep(200); }
try { await ticker.wrap(queue[i], 500); }
catch (err) { console.error(err); i--; await ticker.after(1000); }
}
this.#running = false
}
};
const queue = Array.from(document.querySelectorAll('.sp-wrap')).map((post) => {
const foldTitle = post.querySelector('.sp-head')?.textContent;
const disable = !enableImageReplacementInsideFoldsWithTitleContains.some(e => foldTitle.includes(e));
if(disable) return;
const links = Array.from(post.querySelectorAll('var.postImg'));
if (!links.length) return;
if (autoUnfold) {
post.querySelectorAll('.sp-head').forEach(header => header.classList.add('unfolded'));
}
if (autoPreload || autoUnfold) {
post.querySelectorAll('.sp-body').forEach(body => {
if (autoPreload) body.classList.add('inited');
if (autoUnfold) body.style.display = 'block';
})
}
const queue = [];
for (const link of links) {
const url = link.title;
const parentHref = link.parentNode?.href;
if (!parentHref) {
updateImageUrl(link, url);
continue;
}
if (isBlocked(url)) {
updateImageUrl(link, url);
continue;
}
const updater = getImageUpdater(url, parentHref);
updater ? queue.push(() => updater(link)) : updateImageUrl(link, url);
}
return queue;
}).flat().filter(e => e);
qhandler.run(queue);
async function req(url, method, responseType = undefined) {
return new Promise((res, rej) => {
GM_xmlhttpRequest({
method,
url,
responseType,
onerr: rej,
onload: res
});
});
}
function isBlocked(url) {
return blockedHosts.some(host => url.includes(host));
}
function getImageUpdater(url, parentHref) {
if (url.includes('fastpic.org')) {
return link => handleFastpic(link, parentHref);
} else if (url.includes('imagebam.com')) {
return link => handleImageBam(link, parentHref);
} else if (url.includes('imagevenue.com')) {
return link => handleImageVenue(link, parentHref);
} else if (url.includes('imgbox.com')) {
return link => updateImageUrl(link, url.replace('thumbs', 'images').replace('_t', '_o'));
} else if (url.includes('imgdrive.net')) {
return link => handleImgDrive(link, url.replace('small', 'big'));
} else if (parentHref.includes('turboimagehost.com')) {
return link => handleTurboimagehost(link, parentHref);
}
return null;
}
async function fastpicUrl(link, url) {
return await req(url, 'GET').then(response => {
const match = response.responseText.match(/https?:\/\/i[0-9]{0,3}\.fastpic\.org\/big\/.+\.(?:jpe?g|png|webp)\?.+?"/);
if (![302, 200].includes(response.status)) throw 429;
if (match) updateImageUrl(link, match[0].slice(0, -1));
else updateImageUrl(link, url);
});
}
async function fastpicIsDirectUrl(_, url) {
if(url.includes('/big/')) return Promise.resolve(true);
return await req(url, 'HEAD').then(response => {
return !response.responseHeaders.replaceAll(' ', '').toLowerCase().includes('content-type:text/html');
});
}
async function handleFastpic(link, parentHref) {
const url = (parentHref ? (new URL(parentHref).pathname !== '/' ? parentHref : undefined) : undefined) || link.title;
return await fastpicIsDirectUrl(link, url).then(c => !c ? fastpicUrl(link, url) : updateImageUrl(link, url));
}
async function handleImageBam(link, parentHref) {
return await req(parentHref, 'GET').then(r => {
const match = r.responseText.match(/<img src="(.+?)"[^>]+class="main-image/i);
if (match) updateImageUrl(link, match[1]);
});
}
async function handleImageVenue(link, parentHref) {
return await req(parentHref, 'GET').then(r => {
const match = r.responseText.match(/https?:\/\/cdn-images\.imagevenue\.com\/[a-z0-9]{2}\/[a-z0-9]{2}\/[a-z0-9]{2}\/.+?_o\.(?:jp.?g|png)"/);
if (match) updateImageUrl(link, match[0].slice(0, -1));
});
}
async function handleImgDrive(link, url) {
return await req(url, 'GET', 'blob').then(r => {
const reader = new FileReader();
reader.onload = () => updateImageUrl(link, reader.result);
reader.readAsDataURL(r.response);
});
}
async function handleTurboimagehost(link, url) {
return await req(url, 'GET').then(r => {
const parser = new DOMParser();
const doc = parser.parseFromString(r.responseText, 'text/html');
const src = doc.body.querySelector('img#imageid')?.src
if (src) updateImageUrl(link, src)
});
}
function updateImageUrl(node, url) {
node.title = url;
if (url && autoPreload) {
node.innerHTML = `<img src="${url}" style="max-width:${maxImgWidth}; max-height:${maxImgHeight};"/>`;
}
}
})()
Thank you for the update
I dont know better way
My current easy solution is adding spoilers to the queue in reverse order.
But a good way would be to make a task which runs ~every second and checks which spoilers are open and which are not - and processes images only in opened spoilers ignoring still closed spoilers
Or a button next to a spoiler which opens that spoiler and processes the images inside
imagevenue thumbs just disappear and there's an error after
url example: /forum/viewtopic.php?t=1777687