// ==UserScript== // @name 8chan Single ID Post Opacity with Thread-Specific Cross-Domain Toggle // @namespace https://8chan.moe // @description Halves opacity of posts with unique labelId (based on background-color) if CHECK_UNIQUE_IDS is true, adds a toggle icon to adjust opacity for all posts by ID color in the same thread (including OP), persists toggle state across 8chan.moe and 8chan.se, and handles dynamically added posts // @match https://8chan.moe/*/res/* // @match https://8chan.se/*/res/* // @version 1.9 // @author Anonymous // @grant GM_setValue // @grant GM_getValue // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // Global constant to enable/disable unique ID opacity check // Set to true to halve opacity for IDs with only one post; set to false to disable const CHECK_UNIQUE_IDS = true; // Function to extract board and thread from URL and create a domain-agnostic storage key function getThreadInfo() { const url = window.location.href; const regex = /https:\/\/8chan\.(moe|se)\/([^/]+)\/res\/(\d+)\.html/; const match = url.match(regex); if (match) { return { board: match[2], // e.g., 'a' thread: match[3], // e.g., '23364' storageKey: `toggledColors_${match[2]}_${match[3]}` // e.g., 'toggledColors_a_23364' }; } return null; } // Wait for the DOM to be fully loaded window.addEventListener('load', function() { // Get thread info from URL const threadInfo = getThreadInfo(); if (!threadInfo) { console.error('Could not parse board and thread from URL'); return; } // Use the domain-agnostic storage key const storageKey = threadInfo.storageKey; // Retrieve toggled colors for this thread from storage (or initialize empty array) let toggledColors = GM_getValue(storageKey, []); if (!Array.isArray(toggledColors)) { toggledColors = []; GM_setValue(storageKey, toggledColors); } // Create a map to count occurrences of each background-color const colorCount = new Map(); // Function to update color counts function updateColorCounts() { colorCount.clear(); document.querySelectorAll('.labelId').forEach(label => { const bgColor = label.style.backgroundColor; if (bgColor) { colorCount.set(bgColor, (colorCount.get(bgColor) || 0) + 1); } }); } // Function to create and handle the toggle icon function createToggleIcon(container, bgColor) { // Skip if toggle icon already exists if (container.querySelector('.opacityToggle')) return; const icon = document.createElement('label'); icon.textContent = '⚪'; icon.style.cursor = 'pointer'; icon.style.marginLeft = '5px'; icon.style.color = toggledColors.includes(bgColor) ? '#00ff00' : '#808080'; // Green if toggled, gray if not icon.className = 'opacityToggle glowOnHover coloredIcon'; icon.title = 'Toggle opacity for this ID in this thread'; // Insert icon after extraMenuButton const extraMenuButton = container.querySelector('.extraMenuButton'); if (extraMenuButton) { extraMenuButton.insertAdjacentElement('afterend', icon); } // Click handler for toggling opacity icon.addEventListener('click', () => { // Toggle state for this background-color if (toggledColors.includes(bgColor)) { toggledColors = toggledColors.filter(color => color !== bgColor); } else { toggledColors.push(bgColor); } // Update storage for this thread GM_setValue(storageKey, toggledColors); // Update icon color icon.style.color = toggledColors.includes(bgColor) ? '#00ff00' : '#808080'; // Update opacity for all posts with this background-color (OP and replies) document.querySelectorAll('.innerOP, .innerPost').forEach(p => { const label = p.querySelector('.labelId'); if (label && label.style.backgroundColor === bgColor) { let shouldBeOpaque = false; // Check if ID is toggled if (toggledColors.includes(bgColor)) { shouldBeOpaque = true; } // Check if ID is unique (controlled by CHECK_UNIQUE_IDS) if (CHECK_UNIQUE_IDS && colorCount.get(bgColor) === 1) { shouldBeOpaque = true; } p.style.opacity = shouldBeOpaque ? '0.5' : '1'; } }); }); } // Function to process a single post (OP or regular) function processPost(post, isOP = false) { const labelId = post.querySelector('.labelId'); if (labelId) { const bgColor = labelId.style.backgroundColor; if (bgColor) { let shouldBeOpaque = false; // Check if ID is toggled if (toggledColors.includes(bgColor)) { shouldBeOpaque = true; } // Check if ID is unique (controlled by CHECK_UNIQUE_IDS) if (CHECK_UNIQUE_IDS && colorCount.get(bgColor) === 1) { shouldBeOpaque = true; } // Set initial opacity: 0.5 for toggled or unique IDs, 1 otherwise post.style.opacity = shouldBeOpaque ? '0.5' : '1'; // Add toggle icon to .opHead.title (OP) or .postInfo.title (regular) const title = post.querySelector(isOP ? '.opHead.title' : '.postInfo.title'); if (title) { createToggleIcon(title, bgColor); } } } } // Initial processing: Update color counts and process existing posts updateColorCounts(); // Process OP post (.innerOP) const opPost = document.querySelector('.innerOP'); if (opPost) { processPost(opPost, true); } // Process existing regular posts (.innerPost) document.querySelectorAll('.innerPost').forEach(post => { processPost(post, false); }); // Set up MutationObserver to detect new posts const postsContainer = document.querySelector('.divPosts'); if (postsContainer) { const observer = new MutationObserver((mutations) => { let newPosts = false; mutations.forEach(mutation => { if (mutation.addedNodes.length) { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && node.matches('.postCell')) { const innerPost = node.querySelector('.innerPost'); if (innerPost) { newPosts = true; } } }); } }); if (newPosts) { // Update color counts to include new posts updateColorCounts(); // Process new posts document.querySelectorAll('.innerPost').forEach(post => { // Only process posts without opacity set to avoid reprocessing if (!post.style.opacity) { processPost(post, false); } }); // Reapply opacity to all posts for unique IDs and toggled colors document.querySelectorAll('.innerOP, .innerPost').forEach(p => { const label = p.querySelector('.labelId'); if (label && label.style.backgroundColor) { const bgColor = label.style.backgroundColor; let shouldBeOpaque = false; // Check if ID is toggled if (toggledColors.includes(bgColor)) { shouldBeOpaque = true; } // Check if ID is unique (controlled by CHECK_UNIQUE_IDS) if (CHECK_UNIQUE_IDS && colorCount.get(bgColor) === 1) { shouldBeOpaque = true; } p.style.opacity = shouldBeOpaque ? '0.5' : '1'; } }); } }); observer.observe(postsContainer, { childList: true, subtree: true }); } }); })();