您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
混合觸發模式:滑鼠游標自動展開查看更多 + 點讚 + 影片音量調整 + 左下角控制面板
当前为
// ==UserScript== // @name Facebook 自動展開與互動增強 // @namespace http://tampermonkey.net/ // @version 2025.05.18.13 // @description 混合觸發模式:滑鼠游標自動展開查看更多 + 點讚 + 影片音量調整 + 左下角控制面板 // @author You // @match https://www.facebook.com/* // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function() { const CLICK_INTERVAL = 500; const HIDDEN_STYLE_SELECTOR = '.xwib8y2.x1y1aw1k.xwya9rg.x1n2onr6'; const EXPANDED_CONTAINER_SELECTOR = '.xh8yej3.xrgej4m.xcatxm7.xryxfnj.x1plvlek.x78zum5.xfkwgsy.x1mh14rs.x58fqnu.xrjkcco.x1qjc9v5.x71s49j.x1a2a7pz.xdt5ytf.x1afcbsf.x1ja2u2z.x1n2onr6'; const state = { lastClickTime: 0, likeCoolingDown: false, panelCollapsed: GM_getValue('panelCollapsed', false), DEFAULT_VOLUME: GM_getValue('DEFAULT_VOLUME', 0.2), COLUMN_COUNT: GM_getValue('COLUMN_COUNT', 3), buttons: { 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), hideStyle: GM_getValue('hideStyleEnabled', false), wideLayout: GM_getValue('wideLayoutEnabled', false) } }; let cachedElements = { panel: null, videoElements: null, lastVideoCheck: 0 }; let observer = null; let eventListeners = []; function cleanup() { observer?.disconnect(); eventListeners.forEach(({element, type, handler}) => { element?.removeEventListener?.(type, handler); }); cachedElements.panel?.remove(); cachedElements = null; } function createControlPanel() { const panel = document.createElement('div'); Object.assign(panel.style, { position: 'fixed', left: '0px', bottom: '30px', zIndex: '9999', display: 'flex', flexDirection: 'column', gap: '5px', backgroundColor: 'transparent', padding: '10px', borderRadius: '8px' }); const createButton = (text, key, action) => { const btn = document.createElement('button'); Object.assign(btn.style, { padding: '8px 12px', border: 'none', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold', width: '40px', textAlign: 'center' }); btn.innerText = text; const handler = () => { state.buttons[key] = !state.buttons[key]; GM_setValue(`${key}Enabled`, state.buttons[key]); updateButtonStyle(btn, state.buttons[key]); action?.(); }; btn.addEventListener('click', handler); eventListeners.push({element: btn, type: 'click', handler}); updateButtonStyle(btn, state.buttons[key]); return btn; }; const buttons = [ createButton('讚', 'like'), createButton('看', 'seeMore'), createButton('回', 'otherExpand'), createButton('音', 'volume', () => state.buttons.volume && processAllVideos()) ]; const volumeControlGroup = createControlGroup([ createSmallButton('-', () => adjustVolume(-0.1)), createSmallButton('+', () => adjustVolume(0.1)) ]); buttons.push(volumeControlGroup); if (hasColumnCountCSS()) { buttons.push( createButton('欄', 'columns', () => state.buttons.columns && applyColumnCount()), createControlGroup([ createSmallButton('-', () => adjustColumnCount(-1)), createSmallButton('+', () => adjustColumnCount(1)) ]) ); } buttons.push(createButton('動', 'hideStyle', toggleStyleVisibility)); if (hasWideLayoutCSS()) { buttons.push(createButton('放', 'wideLayout', toggleWideLayout)); } const collapseBtn = createCollapseButton(); buttons.push(collapseBtn); buttons.forEach(btn => panel.appendChild(btn)); document.body.appendChild(panel); cachedElements.panel = panel; state.panelCollapsed && togglePanelCollapse(); } function createCollapseButton() { const btn = document.createElement('button'); Object.assign(btn.style, { padding: '8px 12px', border: 'none', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold', width: '40px', textAlign: 'center', backgroundColor: '#000000', color: '#FFFFFF' }); btn.innerText = state.panelCollapsed ? 'Δ' : '∇'; const handler = () => { state.panelCollapsed = !state.panelCollapsed; GM_setValue('panelCollapsed', state.panelCollapsed); btn.innerText = state.panelCollapsed ? 'Δ' : '∇'; togglePanelCollapse(); }; btn.addEventListener('click', handler); eventListeners.push({element: btn, type: 'click', handler}); return btn; } function createControlGroup(buttons) { const group = document.createElement('div'); Object.assign(group.style, { display: 'flex', justifyContent: 'space-between', width: '40px', marginTop: '-5px' }); buttons.forEach(btn => group.append(btn)); return group; } function createSmallButton(text, action) { const btn = document.createElement('button'); Object.assign(btn.style, { padding: '2px 0', border: '1px solid #000000', borderRadius: '4px', cursor: 'pointer', fontSize: '12px', width: '20px', textAlign: 'center', backgroundColor: '#000000', color: '#FFFFFF' }); btn.innerText = text; const handler = () => action(); btn.addEventListener('click', handler); eventListeners.push({element: btn, type: 'click', handler}); return btn; } function updateButtonStyle(btn, isActive) { Object.assign(btn.style, { backgroundColor: isActive ? '#1877f2' : '#e4e6eb', color: isActive ? 'white' : '#65676b' }); } function togglePanelCollapse() { const buttons = cachedElements.panel?.querySelectorAll('button') || []; buttons.forEach(btn => { if (!['Δ', '∇', '+', '-'].includes(btn.innerText)) { btn.style.display = state.panelCollapsed ? 'none' : 'block'; } }); } function hasWideLayoutCSS() { return Array.from(document.styleSheets).some(sheet => { try { return Array.from(sheet.cssRules).some(rule => rule.media?.mediaText.includes('min-width: 1900px') ); } catch { return false; } }); } function toggleWideLayout() { Array.from(document.styleSheets).forEach(sheet => { try { Array.from(sheet.cssRules).forEach(rule => { if (rule.media?.mediaText.includes('min-width: 1900px') || rule.media?.mediaText.includes('min-width: 9999px')) { rule.media.mediaText = state.buttons.wideLayout ? rule.media.mediaText.replace('9999px', '1900px') : rule.media.mediaText.replace('1900px', '9999px'); } }); } catch {} }); } function toggleStyleVisibility() { const elements = document.querySelectorAll(HIDDEN_STYLE_SELECTOR); elements.forEach(el => { el.style.display = state.buttons.hideStyle ? 'none' : ''; }); } function hasColumnCountCSS() { return getComputedStyle(document.documentElement).getPropertyValue('--column-count').trim() !== ''; } function applyColumnCount() { if (state.buttons.columns) { document.documentElement.style.setProperty('--column-count', state.COLUMN_COUNT); } } function adjustColumnCount(change) { state.COLUMN_COUNT = Math.max(1, state.COLUMN_COUNT + change); GM_setValue('COLUMN_COUNT', state.COLUMN_COUNT); state.buttons.columns && applyColumnCount(); } function adjustVolume(change) { state.DEFAULT_VOLUME = Math.min(1, Math.max(0, state.DEFAULT_VOLUME + change)); GM_setValue('DEFAULT_VOLUME', state.DEFAULT_VOLUME); state.buttons.volume && processAllVideos(); } function processAllVideos() { const now = Date.now(); if (now - cachedElements.lastVideoCheck > 5000) { cachedElements.videoElements = document.querySelectorAll('video'); cachedElements.lastVideoCheck = now; } cachedElements.videoElements?.forEach(video => { try { if (typeof video.volume === 'number') { video.volume = state.DEFAULT_VOLUME; video.muted = false; } } catch {} }); } function isContainerExpanded(button) { const container = button.closest(EXPANDED_CONTAINER_SELECTOR); return container && getComputedStyle(container).display !== 'none'; } const throttledHandleMouseOver = throttle(handleMouseOver, 200); const debouncedHandleOtherButtons = debounce(handleOtherButtons, 300); function handleMouseOver(event) { const target = event.target; if (state.buttons.seeMore && isSeeMoreButton(target) && checkClickInterval()) { safeClick(target); } if (state.buttons.like && !state.likeCoolingDown) { const likeButton = target.closest('div[aria-label="讚"]'); if (likeButton && likeButton.getAttribute('aria-pressed') !== 'true' && isButtonVisible(likeButton)) { state.likeCoolingDown = true; setTimeout(() => { state.likeCoolingDown = false; }, 1000); safeClick(likeButton); } } } function handleOtherButtons() { if (!state.buttons.otherExpand) return; document.querySelectorAll('div[role="button"]:not([aria-expanded="true"])').forEach(btn => { if (isOtherExpandButton(btn) && checkClickInterval()) { safeClick(btn); } }); } function isSeeMoreButton(element) { return element?.getAttribute?.('role') === 'button' && element.getAttribute('aria-expanded') !== 'true' && element.textContent.trim() === '查看更多'; } function isOtherExpandButton(element) { if (!element?.getAttribute || element.getAttribute('aria-expanded') === 'true') { return false; } const text = element.textContent.trim(); const isReplyButton = text && text !== '查看更多' && (/^查看全部\d+則回覆$/.test(text) || /.+已回覆/.test(text) || /^查看 \d+ 則回覆$/.test(text)); return isReplyButton && isContainerExpanded(element); } function checkClickInterval() { const now = Date.now(); if (now - state.lastClickTime > CLICK_INTERVAL) { state.lastClickTime = now; return true; } return false; } function safeClick(element) { element?.click?.(); } function isButtonVisible(button) { if (!button) return false; const rect = button.getBoundingClientRect(); return rect.width > 0 && rect.height > 0 && rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth); } function throttle(func, limit) { let lastFunc; let lastRan; return function() { const context = this; const args = arguments; if (!lastRan) { func.apply(context, args); lastRan = Date.now(); } else { clearTimeout(lastFunc); lastFunc = setTimeout(function() { if ((Date.now() - lastRan) >= limit) { func.apply(context, args); lastRan = Date.now(); } }, limit - (Date.now() - lastRan)); } }; } function debounce(func, wait) { let timeout; return function() { const context = this, args = arguments; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; } function init() { createControlPanel(); if (state.buttons.seeMore || state.buttons.otherExpand) { const intervalId = setInterval(debouncedHandleOtherButtons, 800); eventListeners.push({element: window, type: 'interval', handler: intervalId}); } observer = new MutationObserver(() => { state.buttons.seeMore && state.buttons.otherExpand && handleOtherButtons(); state.buttons.volume && processAllVideos(); state.buttons.columns && applyColumnCount(); state.buttons.hideStyle && toggleStyleVisibility(); state.buttons.wideLayout !== undefined && toggleWideLayout(); }); observer.observe(document.body, { childList: true, subtree: true }); const mouseOverHandler = throttledHandleMouseOver; document.addEventListener('mouseover', mouseOverHandler); eventListeners.push({element: document, type: 'mouseover', handler: mouseOverHandler}); window.addEventListener('unload', cleanup); } if (document.readyState === 'complete') { init(); } else { const loadHandler = () => { init(); window.removeEventListener('load', loadHandler); }; window.addEventListener('load', loadHandler); } })();