// ==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);
})();