您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically opens spoilers and removes warnings on Bluesky (bsky.app) for feed and individual posts
// ==UserScript== // @name Bluesky Auto-Open Spoilers // @namespace http://tampermonkey.net/ // @version 1.0 // @description Automatically opens spoilers and removes warnings on Bluesky (bsky.app) for feed and individual posts // @match https://bsky.app/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // Set to keep track of processed elements to avoid duplicates const processedElements = new Set(); // Throttle function to limit how often processSpoilers runs let lastRun = 0; const throttleDelay = 500; // Run at most every 500ms function throttleProcessSpoilers() { const now = Date.now(); if (now - lastRun >= throttleDelay) { processSpoilers(); lastRun = now; } } // Function to open spoilers and remove warnings function processSpoilers() { // Select all spoiler buttons in feed items and individual post views const spoilerButtons = document.querySelectorAll( 'div[data-testid^="feedItem-"] button[aria-label*="Suggestive"]:not([data-processed]), ' + 'div[data-testid^="feedItem-"] button[aria-label*="Spoiler"]:not([data-processed]), ' + 'div[data-testid^="postThreadItem-"] button[aria-label*="Suggestive"]:not([data-processed]), ' + 'div[data-testid^="postThreadItem-"] button[aria-label*="Spoiler"]:not([data-processed])' ); spoilerButtons.forEach(button => { // Mark as processed to avoid reprocessing const buttonId = button.getAttribute('data-id') || `${Date.now()}-${Math.random()}`; button.setAttribute('data-id', buttonId); button.setAttribute('data-processed', 'true'); if (processedElements.has(buttonId)) return; processedElements.add(buttonId); if (!button.getAttribute('aria-pressed') || button.getAttribute('aria-pressed') === 'false') { button.click(); // Reveal the spoiler } // Find the parent post container (feed item or thread item) let postContainer = button.closest('div[data-testid^="feedItem-"]') || button.closest('div[data-testid^="postThreadItem-"]'); if (postContainer) { // Remove the entire spoiler button button.remove(); // Find the content container with the image let contentContainer = postContainer.querySelector('div[data-expoimage="true"]'); if (contentContainer) { // Replace the parent content hider with just the image content let contentHider = postContainer.querySelector('div[data-testid="contentHider-post"]'); if (contentHider) { contentHider.innerHTML = contentContainer.outerHTML; } else { // If contentHider is not found, replace the button's parent div let buttonParent = button.closest('div[style*="overflow: hidden"]'); if (buttonParent) { buttonParent.innerHTML = contentContainer.outerHTML; } } } } }); } // Run the function initially, with a delay for direct loads, and on page navigation throttleProcessSpoilers(); setTimeout(throttleProcessSpoilers, 500); // Handle delayed DOM rendering on direct loads // Handle page load or navigation to individual posts window.addEventListener('load', throttleProcessSpoilers); window.addEventListener('popstate', throttleProcessSpoilers); // Handle back/forward navigation // Set up a MutationObserver to handle dynamically loaded content const observer = new MutationObserver((mutations) => { throttleProcessSpoilers(); }); // Observe the document for changes (e.g., new posts or page updates) observer.observe(document.body, { childList: true, subtree: true }); // Clean up the observer when the script is unloaded window.addEventListener('unload', () => { observer.disconnect(); processedElements.clear(); }); })();