Greasy Fork is available in English.
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
当前为
// ==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 2.0
// @author Anonymous
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/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 = false;
// 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
});
}
});
})();