Facebook 自動展開與互動增強

混合觸發模式:滑鼠游標自動展開查看更多 + 點讚 + 影片音量調整 + 左下角控制面板

目前為 2025-05-13 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Facebook 自動展開與互動增強
// @namespace    http://tampermonkey.net/
// @version      2025.05.13.4
// @description  混合觸發模式:滑鼠游標自動展開查看更多 + 點讚 + 影片音量調整 + 左下角控制面板
// @author       You
// @match        https://www.facebook.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license MIT
// ==/UserScript==

(function() {
    // 原有自動展開功能變數
    const CLICK_INTERVAL = 500;
    let lastClickTime = 0;

    // 新增功能變數
    let DEFAULT_VOLUME = GM_getValue('DEFAULT_VOLUME', 0.2);
    let likeCoolingDown = false;
    let COLUMN_COUNT = GM_getValue('COLUMN_COUNT', 3); // 預設欄位數

    // 控制面板狀態變數
    const buttonStates = {
        like: GM_getValue('likeEnabled', true),
        seeMore: GM_getValue('seeMoreEnabled', true),
        otherExpand: GM_getValue('otherExpandEnabled', true),
        volume: GM_getValue('volumeEnabled', true),
        columns: GM_getValue('columnsEnabled', false)
    };

    // === 創建控制面板 ===
    function createControlPanel() {
        const panel = document.createElement('div');
        panel.style.position = 'fixed';
        panel.style.left = '0px';
        panel.style.top = '50%';
        panel.style.zIndex = '9999';
        panel.style.display = 'flex';
        panel.style.flexDirection = 'column';
        panel.style.gap = '5px';
        panel.style.backgroundColor = 'rgba(255,255,255,0.9)';
        panel.style.padding = '10px';
        panel.style.borderRadius = '8px';
        panel.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';

        // 讚按鈕
        const likeBtn = createToggleButton('讚', buttonStates.like, () => {
            buttonStates.like = !buttonStates.like;
            GM_setValue('likeEnabled', buttonStates.like);
            updateButtonStyle(likeBtn, buttonStates.like);
        });

        // 看按鈕
        const seeMoreBtn = createToggleButton('看', buttonStates.seeMore, () => {
            buttonStates.seeMore = !buttonStates.seeMore;
            GM_setValue('seeMoreEnabled', buttonStates.seeMore);
            updateButtonStyle(seeMoreBtn, buttonStates.seeMore);
        });

        // 回按鈕
        const otherExpandBtn = createToggleButton('回', buttonStates.otherExpand, () => {
            buttonStates.otherExpand = !buttonStates.otherExpand;
            GM_setValue('otherExpandEnabled', buttonStates.otherExpand);
            updateButtonStyle(otherExpandBtn, buttonStates.otherExpand);
        });

        // 音按鈕
        const volumeBtn = createToggleButton('音', buttonStates.volume, () => {
            buttonStates.volume = !buttonStates.volume;
            GM_setValue('volumeEnabled', buttonStates.volume);
            updateButtonStyle(volumeBtn, buttonStates.volume);
            if (buttonStates.volume) processAllVideos();
        });

        // 音量微調按鈕組
        const volumeControlGroup = createControlGroup();
        const volumeDownBtn = createSmallButton('-', () => {
            DEFAULT_VOLUME = Math.max(0, DEFAULT_VOLUME - 0.1);
            GM_setValue('DEFAULT_VOLUME', DEFAULT_VOLUME);
            if (buttonStates.volume) processAllVideos();
        });
        const volumeUpBtn = createSmallButton('+', () => {
            DEFAULT_VOLUME = Math.min(1, DEFAULT_VOLUME + 0.1);
            GM_setValue('DEFAULT_VOLUME', DEFAULT_VOLUME);
            if (buttonStates.volume) processAllVideos();
        });
        volumeControlGroup.append(volumeDownBtn, volumeUpBtn);

        // 欄按鈕 (僅當偵測到 --column-count 時顯示)
        let columnsBtn, columnsControlGroup;
        if (hasColumnCountCSS()) {
            columnsBtn = createToggleButton('欄', buttonStates.columns, () => {
                buttonStates.columns = !buttonStates.columns;
                GM_setValue('columnsEnabled', buttonStates.columns);
                updateButtonStyle(columnsBtn, buttonStates.columns);
                if (buttonStates.columns) applyColumnCount();
            });

            // 欄位數微調按鈕組
            columnsControlGroup = createControlGroup();
            const columnDownBtn = createSmallButton('-', () => {
                COLUMN_COUNT = Math.max(1, COLUMN_COUNT - 1);
                GM_setValue('COLUMN_COUNT', COLUMN_COUNT);
                if (buttonStates.columns) applyColumnCount();
            });
            const columnUpBtn = createSmallButton('+', () => {
                COLUMN_COUNT += 1;
                GM_setValue('COLUMN_COUNT', COLUMN_COUNT);
                if (buttonStates.columns) applyColumnCount();
            });
            columnsControlGroup.append(columnDownBtn, columnUpBtn);
        }

        // 組合控制面板
        panel.append(likeBtn, seeMoreBtn, otherExpandBtn, volumeBtn, volumeControlGroup);
        if (columnsBtn) {
            panel.append(columnsBtn, columnsControlGroup);
        }
        document.body.appendChild(panel);
    }

    function createToggleButton(text, initialState, onClick) {
        const btn = document.createElement('button');
        btn.innerText = text;
        btn.style.padding = '8px 12px';
        btn.style.border = 'none';
        btn.style.borderRadius = '4px';
        btn.style.cursor = 'pointer';
        btn.style.fontWeight = 'bold';
        btn.style.width = '40px';
        btn.style.textAlign = 'center';
        btn.addEventListener('click', onClick);
        updateButtonStyle(btn, initialState);
        return btn;
    }

    function createSmallButton(text, onClick) {
        const btn = document.createElement('button');
        btn.innerText = text;
        btn.style.padding = '2px 8px';
        btn.style.border = '1px solid #ddd';
        btn.style.borderRadius = '4px';
        btn.style.cursor = 'pointer';
        btn.style.fontSize = '12px';
        btn.style.width = '20px';
        btn.style.textAlign = 'center';
        btn.style.backgroundColor = '#f0f2f5';
        btn.addEventListener('click', onClick);
        return btn;
    }

    function createControlGroup() {
        const group = document.createElement('div');
        group.style.display = 'flex';
        group.style.justifyContent = 'space-between';
        group.style.width = '40px';
        group.style.marginTop = '-5px';
        return group;
    }

    function updateButtonStyle(btn, isActive) {
        if (isActive) {
            btn.style.backgroundColor = '#1877f2';
            btn.style.color = 'white';
        } else {
            btn.style.backgroundColor = '#e4e6eb';
            btn.style.color = '#65676b';
        }
    }

    // 檢查是否存在 --column-count CSS 變數
    function hasColumnCountCSS() {
        return getComputedStyle(document.documentElement).getPropertyValue('--column-count').trim() !== '';
    }

    // 應用欄位數設定
    function applyColumnCount() {
        if (!buttonStates.columns) return;
        document.documentElement.style.setProperty('--column-count', COLUMN_COUNT);
    }

    // === 修改原有功能以支持開關 ===
    document.addEventListener('mouseover', function(event) {
        const target = event.target;
        if (buttonStates.seeMore && isSeeMoreButton(target) && checkClickInterval()) {
            safeClick(target);
        }
    });

    function handleOtherButtons() {
        if (!buttonStates.otherExpand) return;

        document.querySelectorAll('div[role="button"]:not([aria-expanded="true"])').forEach(btn => {
            if (isOtherExpandButton(btn) && checkClickInterval()) {
                safeClick(btn);
            }
        });
    }

    document.addEventListener('mouseover', function(event) {
        if (!buttonStates.like || likeCoolingDown) return;

        const target = event.target.closest('div[aria-label="讚"]');
        if (target &&
            target.getAttribute('aria-pressed') !== 'true' &&
            isButtonVisible(target)
        ) {
            likeCoolingDown = true;
            setTimeout(() => likeCoolingDown = false, 1000);
            safeClick(target);
            console.log('已自動點讚');
        }
    });

    function processAllVideos() {
        if (!buttonStates.volume) return;
        document.querySelectorAll('video').forEach(adjustVideoVolume);
    }

    // === 原有功能函數保持不變 ===
    function isSeeMoreButton(element) {
        return (
            element.getAttribute('role') === 'button' &&
            element.getAttribute('aria-expanded') !== 'true' &&
            element.innerText === '查看更多'
        );
    }

    function isOtherExpandButton(element) {
        return (
            element.innerText !== '查看更多' &&
            (/^查看全部\d+則回覆$/.test(element.innerText) ||
             /.+已回覆/.test(element.innerText) ||
             /^查看 \d+ 則回覆$/.test(element.innerText))
        );
    }

    function checkClickInterval() {
        const now = Date.now();
        if (now - lastClickTime > CLICK_INTERVAL) {
            lastClickTime = now;
            return true;
        }
        return false;
    }

    function safeClick(element) {
        try {
            element.click();
            console.log('已展開:', element.innerText.trim());
        } catch (e) {
            console.warn('展開失敗:', e);
        }
    }

    function adjustVideoVolume(video) {
        if (video && video.volume !== DEFAULT_VOLUME) {
            video.volume = DEFAULT_VOLUME;
            video.muted = false;
        }
    }

    function isButtonVisible(button) {
        const rect = button.getBoundingClientRect();
        return rect.width > 0 && rect.height > 0;
    }

    // === 初始化 ===
    function init() {
        createControlPanel();

        if (buttonStates.seeMore || buttonStates.otherExpand) {
            handleOtherButtons();
            setInterval(handleOtherButtons, 800);
        }

        new MutationObserver(() => {
            if (buttonStates.seeMore || buttonStates.otherExpand) handleOtherButtons();
            if (buttonStates.volume) processAllVideos();
            if (buttonStates.columns) applyColumnCount();
        }).observe(document.body, { childList: true, subtree: true });

        if (buttonStates.volume) processAllVideos();
        if (buttonStates.columns) applyColumnCount();
    }

    window.addEventListener('load', init);
})();