Greasy Fork 支持简体中文。

Quora Unblur Images

Unblur quora images and other utilities

// ==UserScript==
// @name         Quora Unblur Images
// @namespace    quora-unblur-images
// @version      0.6.3
// @description  Unblur quora images and other utilities
// @author       PikaTer
// @match        https://*.quora.com/*
// @exclude      https://*.quora.com/google_/*
// @icon         https://raw.githubusercontent.com/PikaTer/quora-unblur-images/main/favicon.ico
// @license      MIT
// @resource     material_icons https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,[email protected],100..700,0..1,-50..200
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM.setValue
// @grant        GM.getValue
// @grant        GM_addStyle
// @grant        GM_getResourceText
// ==/UserScript==

(function () {
    'use strict';

    /// ---- FUNCTIONS ---- ///

    // Remove Image Blur
    function removeBlur() {
        const images = document.querySelectorAll('.q-box[style*="filter"], .unzoomed');

        images.forEach(image => {
            if (window.getComputedStyle(image).getPropertyValue('filter') != 'blur(0px)') {
                image.style.filter = 'none';
                image.classList.add('unblured');
            }
        });

        setSessionSetting('autoUnblur', true);
    }

    // Blur Images
    function addBlur() {
        const images = document.querySelectorAll('.unblured');

        images.forEach(image => {
            image.style.filter = 'blur(30px)';
            image.classList.remove('unblured');
        });

        setSessionSetting('autoUnblur', false);
    }

    // Expand Posts
    function expandPosts() {
        // TODO: Find a better way to form the selector
        [...document.querySelectorAll('.q-click-wrapper.qu-display--block.qu-tapHighlight--none.qu-cursor--pointer:not(.qu-color--gray,[role="listitem"],.expanded)')].map(post => {
            post.dispatchEvent(clickEvent);
            post.classList.add('expanded');
        });

        // Mobile version sometimes having to click multiple times to fully expand a post
        [...document.querySelectorAll('.puppeteer_test_read_more_button')].map(button => button.dispatchEvent(clickEvent))
    }

    // Observe the DOM for New Post Loaded
    // Credit: @vsync [https://stackoverflow.com/a/14570614]
    function observeDOM(targetNode) {
        const config = { childList: true, subtree: true };

        const callback = (mutationsList, observer) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    getSessionSetting('autoUnblur') && removeBlur();
                    getSessionSetting('autoExpand') && expandPosts();
                }
            }
        };

        if (MutationObserver) {
            const mutationObserver = new MutationObserver(callback);

            mutationObserver.observe(targetNode, config);
        }
        else if (window.addEventListener) {
            targetNode.addEventListener('DOMNodeInserted', callback, false);
            targetNode.addEventListener('DOMNodeRemoved', callback, false);
        }

        getSessionSetting('autoUnblur') && removeBlur();
        getSessionSetting('autoExpand') && expandPosts();
    }

    // Get The Content Node To Add It Into Observer Watch
    function getMainContentNode() {
        let targetNodes = [];

        targetNodes.push(document.getElementById('mainContent')); // Default
        targetNodes.push(document.querySelector('.dom_annotate_multifeed_tribe_top_items')); // Quora Spaces
        targetNodes.push(document.querySelector('.dom_annotate_multifeed_tribe_page')); // Quora Spaces
        targetNodes.push(document.querySelector('.puppeteer_test_question_main')); // Mobile
        targetNodes.push(document.querySelector('#root > div > div.q-box > div > div:nth-child(2) > div:nth-child(4) > div')); // Mobile Profile
        targetNodes.push(document.querySelector('.dom_annotate_multifeed_home')); // Mobile Homepage

        let retries = 0;
        let timeOut = 1000;

        let filteredNodes = targetNodes.filter(value => value !== null)

        if (filteredNodes.length > 0) {
            for (const node of filteredNodes) {
                observeDOM(node)
            }
        } else {
            retries++
            if (retries > 30) timeOut = 5000 // Reduce looping frequency after certain number of retries

            setTimeout(getMainContentNode, timeOut);
        }
    }

    // Initialize GUI (buttons) at bottom left of the page, to control settings
    function initGUI() {
        const material_icons = GM_getResourceText('material_icons')
        GM_addStyle(material_icons);

        // Add GUI Container
        const guiContainer = document.createElement('div');
        guiContainer.id = 'quora-unblur-images-gui-container';
        guiContainer.classList.add('qui-gui-container');

        // Buttons Object Configuration
        const buttonsConfig = {
            settingsButton: {
                id: 'quora-unblur-images-settings-button',
                title: 'User Settings',
                innerHTML: 'settings',
                classNames: ['qui-settings-btn', 'material-symbols-rounded'],
                elementType: 'button',
                hideWhenToggle: true,
                onClick: () => { toggleSettingsPanel(); },
            },
            unblurImageButton: {
                id: 'quora-unblur-images-unblur-image-button',
                title: 'Unblur Images',
                innerHTML: 'visibility',
                classNames: ['qui-unblur-image-btn', 'material-symbols-rounded'],
                elementType: 'button',
                hideWhenToggle: true,
                onClick: () => { removeBlur(); },
            },
            blurImageButton: {
                id: 'quora-unblur-images-blur-image-button',
                title: 'Re-blur Images',
                innerHTML: 'visibility_off',
                classNames: ['qui-blur-image-btn', 'material-symbols-rounded'],
                elementType: 'button',
                hideWhenToggle: true,
                onClick: () => { addBlur(); },
            },
            expandPostsButton: {
                id: 'quora-unblur-images-expand-posts-button',
                title: 'Expand Posts',
                innerHTML: 'unfold_more',
                classNames: ['qui-expand-posts-btn', 'material-symbols-rounded'],
                elementType: 'button',
                hideWhenToggle: true,
                onClick: () => { expandPosts(); },
            },
            expandGUIContainerButton: {
                id: 'quora-unblur-images-expand-gui-container-button',
                title: getGlobalSetting('hideGUIButtons') ? 'Expand GUI Container' : 'Collapse GUI Container',
                innerHTML: getGlobalSetting('hideGUIButtons') ? 'arrow_forward_ios' : 'arrow_back_ios',
                classNames: ['qui-expand-gui-container-btn', 'material-symbols-rounded'],
                elementType: 'span',
                hideWhenToggle: false,
                onClick: () => { toggleGUIContainerButtons(); },
            }
        }

        // Create Buttons
        for (const button in buttonsConfig) {
            // Create Button
            const buttonElement = document.createElement(buttonsConfig[button].elementType);

            // Set Button Attributes
            buttonElement.id = buttonsConfig[button].id;
            buttonElement.classList.add(...buttonsConfig[button].classNames);
            buttonElement.setAttribute('title', buttonsConfig[button].title);
            buttonElement.innerHTML = buttonsConfig[button].innerHTML;
            buttonElement.addEventListener('click', buttonsConfig[button].onClick);
            if (buttonsConfig[button].hideWhenToggle && getGlobalSetting('hideGUIButtons')) { buttonElement.style.display = 'none'; }

            // Append Button To GUI Container
            guiContainer.appendChild(buttonElement);
        }

        // Create Settings Panel
        const settingsPanel = document.createElement('div');
        settingsPanel.id = 'quora-unblur-images-settings-panel';
        settingsPanel.classList.add('qui-settings-panel');
        settingsPanel.style.display = 'none';

        // Settings Panel Title & Content
        const settingsPanelContent = `
            <h3 class="qui-settings-panel-title">
                User Settings
            </h3>
            <div class="qui-settings-panel-item-container">
                <span>Auto Unblur Images</span>
                <label for="quora-unblur-images-auto-unblur-checkbox" class="switch">
                    <input type="checkbox" id="quora-unblur-images-auto-unblur-checkbox" ${getGlobalSetting('autoUnblur') ? 'checked' : ''}>
                    <span class="slider"></span>
                </label>
            </div>
            <div class="qui-settings-panel-item-container">
                <span>Auto Expand Posts</span>
                <label for="quora-unblur-images-auto-expand-checkbox" class="switch">
                    <input type="checkbox" id="quora-unblur-images-auto-expand-checkbox" ${getGlobalSetting('autoExpand') ? 'checked' : ''}>
                    <span class="slider"></span>
                </label>
            </div>
        `
        settingsPanel.innerHTML = settingsPanelContent;

        // Append Settings Panel To GUI Container
        guiContainer.appendChild(settingsPanel);

        // Append GUI Container To Body
        document.body.appendChild(guiContainer);

        // Add Event Listener To Checkboxes
        document.getElementById('quora-unblur-images-auto-unblur-checkbox').addEventListener('change', function () {
            setGlobalSetting('autoUnblur', this.checked);
        });
        document.getElementById('quora-unblur-images-auto-expand-checkbox').addEventListener('change', function () {
            setGlobalSetting('autoExpand', this.checked);
        });
    }

    // Toggle GUI Container Buttons
    function toggleGUIContainerButtons() {
        // Buttons Array To Toggle
        const buttons = [
            'quora-unblur-images-settings-button',
            'quora-unblur-images-unblur-image-button',
            'quora-unblur-images-blur-image-button',
            'quora-unblur-images-expand-posts-button'
        ];

        // Get Expand / Collapse Button
        const expandGUIContainerButton = document.getElementById('quora-unblur-images-expand-gui-container-button');

        // Expand GUI Container Buttons
        const hideGUIButtons = !getGlobalSetting('hideGUIButtons');

        const display = hideGUIButtons ? 'none' : 'inline-block';
        const innerHTML = hideGUIButtons ? 'arrow_forward_ios' : 'arrow_back_ios';
        const title = hideGUIButtons ? 'Expand GUI Container' : 'Collapse GUI Container';

        // Toggle Buttons
        buttons.forEach(buttonId => {
            const button = document.getElementById(buttonId);
            button.style.display = display;
        });

        // Toggle Expand GUI Container Button
        expandGUIContainerButton.innerHTML = innerHTML;
        expandGUIContainerButton.setAttribute('title', title);
        setGlobalSetting('hideGUIButtons', hideGUIButtons);

        // Hide Settings Panel
        if (hideGUIButtons) toggleSettingsPanel(false);
    }

    // Toggle Settings Panel
    function toggleSettingsPanel(show = null) {
        const settingsPanel = document.getElementById('quora-unblur-images-settings-panel');
        const settingsButton = document.getElementById('quora-unblur-images-settings-button');

        const showPanel = show ?? settingsPanel.style.display == 'none' ? true : false;

        showPanel ? settingsPanel.style.display = 'block' : settingsPanel.style.display = 'none';
        showPanel ? settingsButton.classList.add('qui-settings-btn-hover') : settingsButton.classList.remove('qui-settings-btn-hover');
    }

    // Get Global Setting
    function getGlobalSetting(settingName) {
        return GM_getValue(settingName, globalDefaults[settingName]);
    }

    // Set Global Setting
    function setGlobalSetting(settingName, settingValue) {
        GM_setValue(settingName, settingValue);
        // Update Session Setting
        setSessionSetting(settingName, settingValue);
    }

    // Get Session Setting
    function getSessionSetting(settingName) {
        switch (settingName) {
            case 'autoUnblur':
                // Do not auto unblur images if the global setting is false
                return getGlobalSetting('autoUnblur') ? sessionSettings[settingName] : false;
            default:
                return sessionSettings[settingName];
        }
    }

    // Set Session Setting
    function setSessionSetting(settingName, settingValue) {
        sessionSettings[settingName] = settingValue;
    }

    /// ---- GLOBAL VARIABLES ---- ///

    // Define Mouse Click Event
    const clickEvent = new MouseEvent("click", {
        "bubbles": true,
        "cancelable": false
    });

    // Define Global Defaluts
    const globalDefaults = {
        autoUnblur: true,
        autoExpand: false,
        hideGUIButtons: false,
    }

    // Define Session Settings
    const sessionSettings = {
        autoUnblur: getGlobalSetting('autoUnblur'),
        autoExpand: getGlobalSetting('autoExpand'),
    }

    /// ---- CSS ---- ///
    GM_addStyle(`
        .material-symbols-rounded {
            font-variation-settings:
            'FILL' 0,
            'wght' 400,
            'GRAD' 0,
            'opsz' 24
        }

        .qui-gui-container {
            position: fixed;
            bottom: 5px;
            z-index: 9999;
            padding: 10px;
            font-size: 14px;
            display: flex;
            flex-direction: row;
            align-items: center;
        }

        .qui-settings-btn, 
        .qui-unblur-image-btn, 
        .qui-blur-image-btn, 
        .qui-expand-posts-btn {
            background: #262626;
            color: #ccc;
            border: 1px solid #ccc;
            border-radius: 5px;
            box-shadow: 0 0 2px #ccc;
            padding: 5px;
            margin-right: 8px;
            cursor: pointer;
        }

        .qui-settings-btn {
            color: #F52936;
        }

        .qui-settings-btn:hover,
        .qui-unblur-image-btn:hover,
        .qui-blur-image-btn:hover,
        .qui-expand-posts-btn:hover {
            background: #333;
            color: #fff;
            border: 1px solid #fff;
            box-shadow: 0 0 5px #ccc;
        }

        .qui-settings-btn:hover,
        .qui-settings-btn-hover {
            color: #FACE2B;
        }

        .qui-expand-gui-container-btn {
            padding: 5px;
            cursor: pointer;
        }

        .qui-expand-gui-container-btn:hover {
            color: #F52936;
        }

        .qui-settings-panel {
            position: absolute;
            bottom: 55px;
            min-width: 200px;
            max-width: 300px;
            min-height: 50px;
            max-height: 200px;
            background: #262626;
            color: #ccc;
            border: 1px solid #ccc;
            border-radius: 5px;
            box-shadow: 0 0 2px #ccc;
            z-index: 9999;
        }

        .qui-settings-panel-title {
            padding: 5px;
            border-bottom: 1px solid #ccc;
        }

        .qui-settings-panel-item-container {
            padding: 8px;
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: space-between;
        }

        .switch input {
            display: none;
        }

        .switch {
            display: inline-block;
            width: 30px; /*=w*/
            height: 15px; /*=h*/
            margin: 5px;
            position: relative;
        }

        .slider {
            position: absolute;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            border-radius: 15px;
            box-shadow: 0 0 0 2px #777, 0 0 4px #777;
            cursor: pointer;
            border: 4px solid transparent;
            overflow: hidden;
            transition: 0.2s;
        }

        .slider:before {
            position: absolute;
            content: "";
            width: 100%;
            height: 100%;
            background-color: #777;
            border-radius: 30px;
            transform: translateX(-15px); /*translateX(-(w-h))*/
            transition: 0.2s;
        }

        input:checked + .slider:before {
            transform: translateX(15px); /*translateX(w-h)*/
            background-color: #FACE2B;
        }

        input:checked + .slider {
            box-shadow: 0 0 0 2px #FACE2B, 0 0 8px #FACE2B;
        }

        .switch200 .slider:before {
            width: 200%;
            transform: translateX(-15px); /*translateX(-(w-h))*/
        }

        .switch200 input:checked + .slider:before {
            background-color: red;
        }

        .switch200 input:checked + .slider {
            box-shadow: 0 0 0 2px red, 0 0 8px red;
        }
    `)

    /// ---- LISTENERS ---- ///

    // Listener To Listen To Keypresses Events (Shortcuts to utilities)
    document.addEventListener("keydown", function (zEvent) {
        // Expand Posts
        if (zEvent.ctrlKey && zEvent.altKey && zEvent.key === "e") {
            expandPosts();
        }
        // Unblur Images
        if (zEvent.ctrlKey && zEvent.altKey && zEvent.key === "b") {
            removeBlur();
        }
        // Blur Images
        if (zEvent.ctrlKey && zEvent.altKey && zEvent.key === "r") {
            addBlur();
        }
    });

    // Hide Settings Panel
    document.addEventListener('click', function (event) {
        if (!event.target.closest('#quora-unblur-images-settings-panel') && !event.target.closest('#quora-unblur-images-settings-button')) {
            toggleSettingsPanel(false);
        }
    });

    /// ---- MAIN SCRIPT ---- ///

    // Try To Get Target Node Into Observer Watch
    initGUI();
    getMainContentNode();
})();