// ==UserScript== // @name 8chan Spoiler Disabler + Extra // @namespace https://greasyfork.org/en/scripts/533173-8chan-spoiler-disabler // @version 1.5.1 // @description Reveals all spoilers, text and from files along with a couple extra features for catalog usage. // @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, excluding overboard pages (function() { const currentPath = window.location.pathname; // Check if the path matches a board's main page (e.g., /v/, /a/) but not overboard pages if (currentPath.match(/^\/[a-zA-Z0-9]+\/$/) && !currentPath.match(/^\/(sfw|overboard)\/$/)) { // 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'; }); })();