Bluesky Unified Block & Hide

Automatically hides Bluesky posts immediately after confirming the block action using the native Block button.

目前為 2024-12-27 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Bluesky Unified Block & Hide
// @namespace    https://gf.qytechs.cn/en/users/567951-stuart-saddler
// @version      1.0
// @description  Automatically hides Bluesky posts immediately after confirming the block action using the native Block button.
// @icon         https://images.seeklogo.com/logo-png/52/2/bluesky-logo-png_seeklogo-520643.png
// @match        https://bsky.app/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Global variable to store the currently selected username
    let currentUsername = null;

    /**
     * Utility function to log messages with a prefix for easier debugging.
     * @param {string} message - The message to log.
     */
    function log(message) {
        console.log(`[Bluesky Auto Hide Blocked Posts] ${message}`);
    }

    /**
     * Determines if a given element is the "Three Dots" (Post Options) button.
     * @param {Element} element - The DOM element to check.
     * @returns {boolean} - True if it's the "Three Dots" button, else false.
     */
    function isPostOptionsButton(element) {
        if (!element) return false;
        const ariaLabel = element.getAttribute('aria-label') || '';
        return ariaLabel.includes('Open post options menu');
    }

    /**
     * Determines if a given element is the "Block account" button based on its text content.
     * @param {Element} element - The DOM element to check.
     * @returns {boolean} - True if it's the "Block account" button, else false.
     */
    function isBlockButton(element) {
        if (!element) return false;
        const blockButtonText = "Block account";
        return element.textContent.trim() === blockButtonText;
    }

    /**
     * Extracts the username from a post container.
     * @param {Element} postContainer - The post container element.
     * @returns {string|null} - The username or null if not found.
     */
    function getUsernameFromPost(postContainer) {
        if (!postContainer) return null;
        log('Attempting to extract username from post container.');

        // Attempt to find an <a> tag with href containing "/profile/"
        const usernameLink = postContainer.querySelector('a[href*="/profile/"]');
        if (usernameLink) {
            const href = usernameLink.getAttribute('href');
            const parts = href.split('/');
            const username = parts[parts.length - 1] || null;
            if (username) {
                log(`Extracted username: ${username}`);
                return username.toLowerCase(); // Ensure lowercase for consistency
            }
        }

        // Alternative method: Look for a span containing "@" symbol
        const possibleUsernameElements = postContainer.querySelectorAll('span, div');
        for (let el of possibleUsernameElements) {
            const text = el.textContent.trim();
            if (text.startsWith('@')) { // Look for text starting with "@"
                const username = text.substring(1); // Remove "@" symbol
                log(`Extracted username from span: ${username}`);
                return username.toLowerCase(); // Ensure lowercase for consistency
            }
        }

        log('Username could not be extracted from the post.');
        return null;
    }

    /**
     * Hides all posts from the specified username.
     * @param {string} username - The username whose posts should be hidden.
     */
    function hidePostsFromUser(username) {
        if (!username) return;
        log(`Hiding posts from user: @${username}`);

        // Define selectors based on post container identification
        const selector = `div[role="link"][tabindex="0"], div[role="article"], section[role="article"]`;
        const posts = document.querySelectorAll(selector);

        let hiddenCount = 0;
        posts.forEach(post => {
            const postUsername = getUsernameFromPost(post);
            if (postUsername && postUsername === username) {
                post.style.display = 'none';
                log(`Post from @${username} has been hidden.`);
                hiddenCount++;
            }
        });

        log(`Total posts hidden from @${username}: ${hiddenCount}`);
    }

    /**
     * Adds a username to the blocked list and hides their posts.
     * @param {string} username - The username to block.
     */
    function addBlockedUser(username) {
        if (!username) return;
        hidePostsFromUser(username);
    }

    /**
     * Hides all posts from blocked users.
     * (This function is now redundant since we're not maintaining a blocked users list.)
     */
    function hidePostsFromBlockedUsers() {
        // No longer needed as we're not maintaining a blocked users list.
        // Removed to streamline the script.
    }

    /**
     * Initializes the script by hiding posts from blocked users.
     * (This function is now redundant since we're not maintaining a blocked users list.)
     */
    function initializeBlockedUsers() {
        // No longer needed as we're not maintaining a blocked users list.
        // Removed to streamline the script.

        // Optionally, if you still want to hide posts immediately after blocking without relying on storage,
        // you can keep any necessary logic here.
    }

    /**
     * Sets up the listener for the "Three Dots" (Post Options) button to capture the username.
     */
    function setupPostOptionsListener() {
        document.addEventListener('click', function(event) {
            let target = event.target;

            // Traverse up the DOM tree to check if a "Three Dots" button was clicked
            while (target && target !== document.body) {
                if (isPostOptionsButton(target)) {
                    log('"Three Dots" button clicked.');
                    // Find the post container associated with this button
                    const postContainer = target.closest('div[role="link"][tabindex="0"], div[role="article"], section[role="article"]');
                    if (postContainer) {
                        const username = getUsernameFromPost(postContainer);
                        if (username) {
                            currentUsername = username;
                            log(`Current post's username set to: @${username}`);
                        } else {
                            log('Username could not be determined from the post.');
                            currentUsername = null;
                        }
                    } else {
                        log('Post container not found.');
                        currentUsername = null;
                    }
                    break; // Exit once handled
                }
                target = target.parentElement;
            }
        }, true); // Use capture phase
    }

    /**
     * Sets up the listener for the "Block account" button within the menu to handle confirmation.
     */
    function setupBlockButtonListener() {
        document.addEventListener('click', function(event) {
            let target = event.target;

            // Traverse up the DOM tree to check if a "Block account" button was clicked
            while (target && target !== document.body) {
                if (isBlockButton(target)) {
                    log('"Block account" button clicked.');
                    // Do NOT hide posts here; wait for confirmation
                    // The hiding will be handled in the confirmation dialog listener
                    break; // Exit once handled
                }
                target = target.parentElement;
            }
        }, true); // Use capture phase
    }

    /**
     * Sets up a listener for the confirmation button to add the user to the blocked list and hide their posts.
     */
    function setupConfirmationButtonListener() {
        document.addEventListener('click', function(event) {
            const target = event.target;

            // Check if the clicked element or its parent has data-testid="confirmBtn"
            const confirmBtn = target.closest('button[data-testid="confirmBtn"]');
            if (confirmBtn) {
                log('Confirmation button clicked.');
                if (currentUsername) {
                    addBlockedUser(currentUsername);
                    currentUsername = null;
                } else {
                    log('No user recorded for blocking.');
                }
            }
        }, true); // Use capture phase
    }

    /**
     * Utility function to debounce frequent function calls.
     * @param {Function} func - The function to debounce.
     * @param {number} delay - The delay in milliseconds.
     * @returns {Function} - The debounced function.
     */
    function debounce(func, delay) {
        let timeout;
        return function(...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), delay);
        };
    }

    /**
     * Initializes the script by setting up listeners.
     */
    function init() {
        setupPostOptionsListener();
        setupBlockButtonListener();
        setupConfirmationButtonListener();
        // initializeBlockedUsers(); // No longer needed
        log('Bluesky Auto Hide Blocked Posts script has been initialized.');
    }

    /**
     * Waits for the DOM to be fully loaded before initializing the script.
     */
    function waitForDOM() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                setTimeout(init, 500); // Slight delay to ensure all elements are loaded
            });
        } else {
            setTimeout(init, 500);
        }
    }

    // Start the script
    waitForDOM();

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址