// ==UserScript==
// @name 8chan Spoiler Disabler
// @namespace https://greasyfork.org/en/scripts/533173-8chan-spoiler-disabler
// @version 1.5
// @description Disables image and video spoilers on 8chan (moe/se/cc) for both thread and catalog pages by replacing custom and default spoiler placeholders with thumbnails, including dynamically loaded posts.
// @author impregnator
// @match https://8chan.moe/*
// @match https://8chan.se/*
// @match https://8chan.cc/*
// @grant none
// @license MIT
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// Function to process images and replace spoiler placeholders with thumbnails
function processImages(images, isCatalog = false) {
images.forEach(img => {
// Check if the image is a spoiler placeholder (custom or default)
if (img.src.includes('custom.spoiler') || img.src.includes('spoiler.png')) {
let fullFileUrl;
if (isCatalog) {
// Catalog: Get the href from the parent
const link = img.closest('a.linkThumb');
if (link) {
// Construct the thumbnail URL based on the thread URL
fullFileUrl = link.href;
const threadMatch = fullFileUrl.match(/\/([a-z0-9]+)\/res\/([0-9]+)\.html$/i);
if (threadMatch && threadMatch[1] && threadMatch[2]) {
const board = threadMatch[1];
const threadId = threadMatch[2];
// Fetch the thread page to find the actual image URL
fetchThreadImage(board, threadId).then(thumbnailUrl => {
if (thumbnailUrl) {
img.src = thumbnailUrl;
}
});
}
}
} else {
// Thread: Get the parent element containing the full-sized file URL
const link = img.closest('a.imgLink');
if (link) {
// Extract the full-sized file URL
fullFileUrl = link.href;
// Extract the file hash (everything after /.media/ up to the extension)
const fileHash = fullFileUrl.match(/\/\.media\/([a-f0-9]+)\.[a-z0-9]+$/i);
if (fileHash && fileHash[1]) {
// Construct the thumbnail URL using the current domain
const thumbnailUrl = `${window.location.origin}/.media/t_${fileHash[1]}`;
// Replace the spoiler image with the thumbnail
img.src = thumbnailUrl;
}
}
}
}
});
}
// Function to fetch the thread page and extract the thumbnail URL
async function fetchThreadImage(board, threadId) {
try {
const response = await fetch(`https://${window.location.host}/${board}/res/${threadId}.html`);
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
// Find the first image in the thread's OP post
const imgLink = doc.querySelector('.uploadCell a.imgLink');
if (imgLink) {
const fullFileUrl = imgLink.href;
const fileHash = fullFileUrl.match(/\/\.media\/([a-f0-9]+)\.[a-z0-9]+$/i);
if (fileHash && fileHash[1]) {
return `${window.location.origin}/.media/t_${fileHash[1]}`;
}
}
return null;
} catch (error) {
console.error('Error fetching thread image:', error);
return null;
}
}
// Process existing images on page load
const isCatalogPage = window.location.pathname.includes('catalog.html');
if (isCatalogPage) {
const initialCatalogImages = document.querySelectorAll('.catalogCell a.linkThumb img');
processImages(initialCatalogImages, true);
} else {
const initialThreadImages = document.querySelectorAll('.uploadCell img');
processImages(initialThreadImages, false);
}
// Set up MutationObserver to handle dynamically added posts
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.addedNodes.length) {
// Check each added node for new images
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (isCatalogPage) {
const newCatalogImages = node.querySelectorAll('.catalogCell a.linkThumb img');
processImages(newCatalogImages, true);
} else {
const newThreadImages = node.querySelectorAll('.uploadCell img');
processImages(newThreadImages, false);
}
}
});
}
});
});
// Observe changes to the document body, including child nodes and subtrees
observer.observe(document.body, {
childList: true,
subtree: true
});
})();
//Opening all posts from the catalog in a new tag section
// Add click event listener to catalog thumbnail images
document.addEventListener('click', function(e) {
// Check if the clicked element is an image inside a catalog cell
if (e.target.tagName === 'IMG' && e.target.closest('.catalogCell')) {
// Find the parent link with class 'linkThumb'
const link = e.target.closest('.linkThumb');
if (link) {
// Prevent default link behavior
e.preventDefault();
// Open the thread in a new tab
window.open(link.href, '_blank');
}
}
});
//Automatically redirect to catalog section
// Redirect to catalog if on a board's main page
(function() {
const currentPath = window.location.pathname;
// Check if the path matches a board's main page (e.g., /v/, /a/, etc.)
if (currentPath.match(/^\/[a-zA-Z0-9]+\/$/)) {
// Redirect to the catalog page
window.location.replace(currentPath + 'catalog.html');
}
})();
//Text spoiler revealer
// Automatically reveal all spoilers
(function() {
// Find all elements with class 'spoiler'
const spoilers = document.querySelectorAll('span.spoiler');
spoilers.forEach(spoiler => {
// Override default spoiler styles to make text visible
spoiler.style.background = 'none';
spoiler.style.color = 'inherit';
spoiler.style.textShadow = 'none';
});
})();