BlueSky Handle Cleaner

Removes the ".bsky.social" part from handles visually while preserving functionality

// ==UserScript==
// @name         BlueSky Handle Cleaner
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Removes the ".bsky.social" part from handles visually while preserving functionality
// @author       mopolo
// @match        https://*.bsky.app/*
// @match        https://*.bsky.dev/*
// @match        https://bsky.app/*
// @match        https://bsky.dev/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // Function to process text nodes
    function processTextNode(node) {
        // Skip processing for nodes inside style or script tags
        if (node.parentNode && (node.parentNode.nodeName === 'STYLE' || node.parentNode.nodeName === 'SCRIPT')) {
            return;
        }

        const originalText = node.nodeValue;
        // Match both .bsky.social and other potential domains like .bsky.dev
        const newText = originalText
            .replace(/(@[\w-]+)\.bsky\.social/g, '$1\u00A0')
            .replace(/(@[\w-]+)\.bsky\.dev/g, '$1\u00A0')
            .replace(/(@[\w-]+)\.bsky\.app/g, '$1\u00A0');
        if (originalText !== newText) {
            node.nodeValue = newText;
        }
    }

    // Process all existing text nodes
    function processExistingNodes() {
        const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
        const textNodes = [];
        let node;
        while (node = walker.nextNode()) {
            textNodes.push(node);
        }
        textNodes.forEach(processTextNode);
    }

    // Set up a mutation observer to process any new nodes
    function observeDOMChanges() {
        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                // Process added nodes
                if (mutation.addedNodes && mutation.addedNodes.length > 0) {
                    for (let i = 0; i < mutation.addedNodes.length; i++) {
                        const addedNode = mutation.addedNodes[i];
                        // Process text nodes directly
                        if (addedNode.nodeType === Node.TEXT_NODE) {
                            processTextNode(addedNode);
                        }
                        // Process text nodes within the added node
                        if (addedNode.nodeType === Node.ELEMENT_NODE) {
                            const walker = document.createTreeWalker(addedNode, NodeFilter.SHOW_TEXT, null, false);
                            let textNode;
                            while (textNode = walker.nextNode()) {
                                processTextNode(textNode);
                            }
                        }
                    }
                }
                // Process character data changes
                if (mutation.type === 'characterData') {
                    processTextNode(mutation.target);
                }
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            characterData: true
        });
    }

    // Run our functions when the page is loaded
    function init() {
        console.log('BlueSky Handle Cleaner: Script started');
        // Initial processing might need to wait for dynamic content to load
        processExistingNodes();
        // Set up observer to catch new content
        observeDOMChanges();
        // Run additional passes to catch any dynamically loaded content
        setTimeout(processExistingNodes, 1500);
        setTimeout(processExistingNodes, 3000);
    }

    // Ensure script runs regardless of how the page is loaded
    if (document.readyState === 'complete') {
        // Page is already loaded
        init();
    } else {
        // Wait for page to load
        window.addEventListener('load', init);
        // Also try with DOMContentLoaded as backup
        window.addEventListener('DOMContentLoaded', () => {
            setTimeout(init, 500);
        });
    }
})();