Bluesky URL Mention Sidebar

Display a sidebar with all mentions of the current URL on Bluesky, togglable via Alt+X, with logging, disabled in iframes, drag-resizeable, closeable, updates on navigation without monkey-patching, hidden if no mentions

当前为 2024-12-19 提交的版本,查看 最新版本

// ==UserScript==
// @name         Bluesky URL Mention Sidebar
// @namespace    http://tampermonkey.net/
// @version      1.9
// @description  Display a sidebar with all mentions of the current URL on Bluesky, togglable via Alt+X, with logging, disabled in iframes, drag-resizeable, closeable, updates on navigation without monkey-patching, hidden if no mentions
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      public.api.bsky.app
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // Check if in iframe; if so, do not run
    if (window.self !== window.top) {
        console.log('[Bluesky Sidebar]: Running in iframe, script disabled');
        return;
    }

    console.log('[Bluesky Sidebar]: Script initialized at top-level window');

    let isResizing = false;
    let startX = 0;
    let startWidth = 450; // default width
    let sidebar = null;
    let lastUrl = window.location.href;

    // Add global keydown listener for Alt+X to toggle the sidebar
    document.addEventListener('keydown', (e) => {
        // Check for Alt+X
        if (e.altKey && e.code === 'KeyX') {
            if (sidebar) {
                if (sidebar.style.display === 'none') {
                    console.log('[Bluesky Sidebar]: Alt+X pressed, opening sidebar');
                    fetchMentions(sidebar);
                } else {
                    console.log('[Bluesky Sidebar]: Alt+X pressed, closing sidebar');
                    sidebar.style.display = 'none';
                }
            }
        }
    });

    function createSidebar() {
        console.log('[Bluesky Sidebar]: Attempting to create sidebar');
        // Check if the sidebar already exists
        if (document.getElementById('bluesky-sidebar')) {
            console.log('[Bluesky Sidebar]: Sidebar already exists. Skipping creation.');
            sidebar = document.getElementById('bluesky-sidebar');
            return;
        }

        // Create sidebar element
        sidebar = document.createElement('div');
        sidebar.id = 'bluesky-sidebar';
        sidebar.innerHTML = `
            <div id="bluesky-sidebar-header">
                <h2>Bluesky Mentions</h2>
                <button id="bluesky-close-btn">X</button>
            </div>
            <p>Loading...</p>
        `;
        document.body.appendChild(sidebar);

        // Create resize handle
        const resizeHandle = document.createElement('div');
        resizeHandle.id = 'bluesky-resize-handle';
        sidebar.appendChild(resizeHandle);

        console.log('[Bluesky Sidebar]: Sidebar created and appended to body');

        GM_addStyle(`
            #bluesky-sidebar {
                position: fixed;
                top: 0;
                right: 0;
                width: ${startWidth}px;
                height: 100%;
                background: #f0f0f0;
                border-left: 1px solid #ccc;
                padding: 10px;
                overflow-y: auto;
                z-index: 10000;
                font-family: sans-serif;
                box-sizing: border-box;
                display: none; /* Initially hidden until we know if mentions exist */
            }
            #bluesky-sidebar-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 10px;
            }
            #bluesky-sidebar-header h2 {
                font-size: 18px;
                margin: 0;
            }
            #bluesky-sidebar p {
                font-size: 14px;
            }
            #bluesky-sidebar blockquote {
                border-left: 2px solid #ccc;
                margin: 10px 0;
                padding-left: 10px;
                background: #fff;
                font-size: 14px;
                line-height: 1.4;
            }
            #bluesky-close-btn {
                background: transparent;
                border: none;
                font-size: 16px;
                cursor: pointer;
                padding: 0 5px;
            }
            #bluesky-close-btn:hover {
                background: #ddd;
            }
            #bluesky-resize-handle {
                position: absolute;
                left: -3px;
                top: 0;
                width: 5px;
                height: 100%;
                cursor: ew-resize;
                background: rgba(0,0,0,0);
                z-index: 10001;
            }
        `);

        console.log('[Bluesky Sidebar]: Styles applied');

        const closeBtn = document.getElementById('bluesky-close-btn');
        closeBtn.addEventListener('click', () => {
            console.log('[Bluesky Sidebar]: Close button clicked');
            sidebar.style.display = 'none';
        });

        const onMouseMove = (e) => {
            if (!isResizing) return;
            const deltaX = startX - e.clientX;
            const newWidth = startWidth + deltaX;
            sidebar.style.width = Math.max(newWidth, 150) + 'px';
        };

        const onMouseUp = (e) => {
            if (isResizing) {
                isResizing = false;
                document.removeEventListener('mousemove', onMouseMove);
                document.removeEventListener('mouseup', onMouseUp);
                startWidth = parseInt(sidebar.style.width, 10);
                console.log('[Bluesky Sidebar]: Resizing ended, final width:', startWidth + 'px');
            }
        };

        resizeHandle.addEventListener('mousedown', (e) => {
            isResizing = true;
            startX = e.clientX;
            startWidth = parseInt(window.getComputedStyle(sidebar).width, 10);
            console.log('[Bluesky Sidebar]: Resizing started at X:', startX, 'Current width:', startWidth + 'px');
            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);
        });

        fetchMentions(sidebar);
    }

    function fetchMentions(sidebar) {
        if (!sidebar) return;
        console.log('[Bluesky Sidebar]: Fetching mentions for URL:', window.location.href);
        lastUrl = window.location.href;
        const currentUrl = window.location.href;
        const apiUrl = `https://public.api.bsky.app/xrpc/app.bsky.feed.searchPosts?q=${encodeURIComponent(currentUrl)}&sort=top`;

        console.log('[Bluesky Sidebar]: Using API URL', apiUrl);

        GM_xmlhttpRequest({
            method: 'GET',
            url: apiUrl,
            headers: {
                'Accept': 'application/json',
            },
            onload: function(response) {
                console.log('[Bluesky Sidebar]: API responded with status', response.status);
                if (response.status === 200) {
                    try {
                        const data = JSON.parse(response.responseText);
                        console.log('[Bluesky Sidebar]: Parsed JSON data:', data);
                        displayMentions(data, sidebar);
                    } catch (e) {
                        console.error('[Bluesky Sidebar]: Error parsing JSON:', e);
                        sidebar.innerHTML = '<h2>Bluesky Mentions</h2><p>Error parsing response.</p>';
                        sidebar.style.display = 'none';
                    }
                } else {
                    console.error('[Bluesky Sidebar]: Non-200 status returned:', response.status);
                    sidebar.innerHTML = '<h2>Bluesky Mentions</h2><p>Error fetching mentions.</p>';
                    sidebar.style.display = 'none';
                }
            },
            onerror: function() {
                console.error('[Bluesky Sidebar]: Error during GM_xmlhttpRequest');
                sidebar.innerHTML = '<h2>Bluesky Mentions</h2><p>Error fetching mentions.</p>';
                sidebar.style.display = 'none';
            }
        });
    }

    function displayMentions(data, sidebar) {
        console.log('[Bluesky Sidebar]: Displaying mentions');

        if (data.posts && data.posts.length > 0) {
            // Only display the sidebar if we have posts
            sidebar.style.display = 'block';

            sidebar.innerHTML = `
                <div id="bluesky-sidebar-header">
                    <h2>Bluesky Mentions</h2>
                    <button id="bluesky-close-btn">X</button>
                </div>
            `;

            const closeBtn = document.getElementById('bluesky-close-btn');
            closeBtn.addEventListener('click', () => {
                console.log('[Bluesky Sidebar]: Close button clicked');
                sidebar.style.display = 'none';
            });

            const resizeHandle = document.createElement('div');
            resizeHandle.id = 'bluesky-resize-handle';
            sidebar.appendChild(resizeHandle);

            let localIsResizing = false;
            let localStartX = 0;
            let localStartWidth = parseInt(window.getComputedStyle(sidebar).width, 10);

            const onMouseMove = (e) => {
                if (!localIsResizing) return;
                const deltaX = localStartX - e.clientX;
                const newWidth = localStartWidth + deltaX;
                sidebar.style.width = Math.max(newWidth, 150) + 'px';
            };

            const onMouseUp = (e) => {
                if (localIsResizing) {
                    localIsResizing = false;
                    document.removeEventListener('mousemove', onMouseMove);
                    document.removeEventListener('mouseup', onMouseUp);
                    localStartWidth = parseInt(sidebar.style.width, 10);
                    console.log('[Bluesky Sidebar]: Resizing ended, final width:', localStartWidth + 'px');
                }
            };

            resizeHandle.addEventListener('mousedown', (e) => {
                localIsResizing = true;
                localStartX = e.clientX;
                localStartWidth = parseInt(window.getComputedStyle(sidebar).width, 10);
                console.log('[Bluesky Sidebar]: Resizing started at X:', localStartX, 'Current width:', localStartWidth + 'px');
                document.addEventListener('mousemove', onMouseMove);
                document.addEventListener('mouseup', onMouseUp);
            });

            data.posts.forEach((post, index) => {
                console.log('[Bluesky Sidebar]: Processing post', index, post);
                const blockquote = document.createElement('blockquote');
                blockquote.className = 'bluesky-embed';
                blockquote.setAttribute('data-bluesky-uri', post.uri);
                blockquote.setAttribute('data-bluesky-cid', post.cid);

                const p = document.createElement('p');
                p.textContent = post.record.text || '[No Text]';
                blockquote.appendChild(p);

                sidebar.appendChild(blockquote);
            });

            const embedScriptSrc = "https://embed.bsky.app/static/embed.js";
            if (!document.querySelector(`script[src="${embedScriptSrc}"]`)) {
                console.log('[Bluesky Sidebar]: Embed script not found, adding it now');
                const script = document.createElement('script');
                script.async = true;
                script.src = embedScriptSrc;
                script.charset = "utf-8";
                document.body.appendChild(script);

                script.addEventListener('load', () => {
                    console.log('[Bluesky Sidebar]: Embed script loaded');
                });
                script.addEventListener('error', (e) => {
                    console.error('[Bluesky Sidebar]: Error loading embed script', e);
                });
            } else {
                console.log('[Bluesky Sidebar]: Embed script already present on page');
                window?.bluesky?.scan();
            }
        } else {
            console.log('[Bluesky Sidebar]: No posts found, hiding sidebar');
            sidebar.style.display = 'none';
        }
    }

    window.addEventListener('load', () => {
        console.log('[Bluesky Sidebar]: window load event fired');
        createSidebar();
    });

    window.addEventListener('popstate', () => {
        console.log('[Bluesky Sidebar]: popstate event detected');
        if (sidebar) fetchMentions(sidebar);
    });

    window.addEventListener('hashchange', () => {
        console.log('[Bluesky Sidebar]: hashchange event detected');
        if (sidebar) fetchMentions(sidebar);
    });

    setInterval(() => {
        if (window.location.href !== lastUrl) {
            console.log('[Bluesky Sidebar]: URL changed detected by polling');
            if (sidebar && sidebar.style.display !== 'none') {
                fetchMentions(sidebar);
            } else {
                lastUrl = window.location.href;
            }
        }
    }, 1000);

})();

QingJ © 2025

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