您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在贴文右上新增一个悬浮截图按钮,按下后可以对贴文进行截图保存,方便与其他人分享
- // ==UserScript==
- // @name Floating Screenshot Button for Facebook Posts
- // @name:zh-TW FaceBook 貼文懸浮截圖按鈕
- // @name:zh-CN FaceBook 贴文悬浮截图按钮
- // @namespace http://tampermonkey.net/
- // @version 3.4
- // @description A floating screenshot button is added to the top-right corner of the post. When clicked, it allows users to capture and save a screenshot of the post, making it easier to share with others.
- // @description:zh-TW 在貼文右上新增一個懸浮截圖按鈕,按下後可以對貼文進行截圖保存,方便與其他人分享
- // @description:zh-CN 在贴文右上新增一个悬浮截图按钮,按下后可以对贴文进行截图保存,方便与其他人分享
- // @author chatgpt
- // @match https://www.facebook.com/*
- // @grant none
- // @require https://cdn.jsdelivr.net/npm/html-to-image@1.11.11/dist/html-to-image.min.js
- // @license MIT
- // ==/UserScript==
- (function () {
- 'use strict';
- // ===== 禁用聚焦樣式,移除藍框或陰影 =====
- const style = document.createElement('style');
- style.textContent = `
- *:focus, *:focus-visible, *:focus-within {
- outline: none !important;
- box-shadow: none !important;
- }
- `;
- document.head.appendChild(style);
- let lastRun = 0; // 上一次主頁按鈕建立的時間
- const debounceDelay = 1000; // 間隔時間(毫秒)
- // ===== 從貼文中取得 fbid(供檔名使用)=====
- function getFbidFromPost(post) {
- const links = Array.from(post.querySelectorAll('a[href*="fbid="], a[href*="story_fbid="]'));
- for (const a of links) {
- try {
- const url = new URL(a.href);
- const fbid = url.searchParams.get('fbid') || url.searchParams.get('story_fbid');
- if (fbid) return fbid;
- } catch (e) { }
- }
- try {
- const dataFt = post.getAttribute('data-ft');
- if (dataFt) {
- const match = dataFt.match(/"top_level_post_id":"(\d+)"/);
- if (match) return match[1];
- }
- } catch (e) { }
- try {
- const url = new URL(window.location.href);
- const fbid = url.searchParams.get('fbid') || url.searchParams.get('story_fbid');
- if (fbid) return fbid;
- } catch (e) { }
- return 'unknownFBID';
- }
- // ===== 主頁貼文的截圖按鈕觀察器 =====
- const observer = new MutationObserver(() => {
- const now = Date.now();
- if (now - lastRun < debounceDelay) return;
- lastRun = now;
- document.querySelectorAll('div.x1lliihq').forEach(post => {
- if (post.dataset.sbtn === '1') return;
- // 排除含社團建議文字的區塊
- const textContent = post.innerText || post.textContent || '';
- if (textContent.includes('社團建議') || textContent.includes('Suggested Groups')) return;
- let btnGroup = post.querySelector('div[role="group"]')
- || post.querySelector('div.xqcrz7y')
- || post.querySelector('div.x1qx5ct2');
- if (!btnGroup) return;
- post.dataset.sbtn = '1'; // 標記已處理
- btnGroup.style.position = 'relative';
- // 建立截圖按鈕
- const btn = document.createElement('div');
- btn.textContent = '📸';
- btn.title = '截圖貼文';
- Object.assign(btn.style, {
- position: 'absolute',
- left: '-40px',
- top: '0',
- width: '32px',
- height: '32px',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- borderRadius: '50%',
- backgroundColor: '#3A3B3C',
- color: 'white',
- cursor: 'pointer',
- zIndex: '9999',
- transition: 'background .2s',
- });
- btn.addEventListener('mouseenter', () => btn.style.backgroundColor = '#4E4F50');
- btn.addEventListener('mouseleave', () => btn.style.backgroundColor = '#3A3B3C');
- // 按下後進行截圖
- btn.addEventListener('click', async e => {
- e.stopPropagation();
- btn.textContent = '⏳';
- btn.style.pointerEvents = 'none';
- try {
- // 嘗試展開貼文內的「查看更多」
- const seeMoreCandidates = post.querySelectorAll('span, a, div, button');
- let clicked = false;
- for (const el of seeMoreCandidates) {
- const text = el.innerText?.trim() || el.textContent?.trim();
- if (!text) continue;
- if (text === '查看更多' || text === '顯示更多' || text === 'See more' || text === 'See More' || text === '…更多') {
- try {
- el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
- clicked = true;
- console.log('已點擊查看更多:', el);
- } catch (err) {
- console.warn('點擊查看更多失敗:', err);
- }
- }
- }
- if (clicked) await new Promise(r => setTimeout(r, 1000));
- await new Promise(r => setTimeout(r, 500));
- const fbid = getFbidFromPost(post);
- const nowDate = new Date();
- const pad = n => n.toString().padStart(2, '0');
- const datetimeStr =
- nowDate.getFullYear().toString() +
- pad(nowDate.getMonth() + 1) +
- pad(nowDate.getDate()) + '_' +
- pad(nowDate.getHours()) + '_' +
- pad(nowDate.getMinutes()) + '_' +
- pad(nowDate.getSeconds());
- const filename = `${fbid}_${datetimeStr}.png`;
- // 等待字型載入
- await document.fonts.ready;
- // 執行截圖
- const dataUrl = await window.htmlToImage.toPng(post, {
- backgroundColor: '#1c1c1d',
- pixelRatio: 2,
- cacheBust: true,
- });
- // 儲存圖片
- const link = document.createElement('a');
- link.href = dataUrl;
- link.download = filename;
- link.click();
- btn.textContent = '✅';
- } catch (err) {
- console.error('截圖錯誤:', err);
- alert('截圖失敗,請稍後再試');
- btn.textContent = '❌';
- }
- // 一秒後還原按鈕狀態
- setTimeout(() => {
- btn.textContent = '📸';
- btn.style.pointerEvents = 'auto';
- }, 1000);
- });
- btnGroup.appendChild(btn);
- });
- });
- observer.observe(document.body, { childList: true, subtree: true });
- // ====== 社團截圖按鈕功能 ======
- let groupObserver = null;
- function initGroupPostObserver() {
- if (groupObserver) return;
- groupObserver = new MutationObserver(() => {
- document.querySelectorAll('div.x1yztbdb.x1n2onr6.xh8yej3.x1ja2u2z').forEach(post => {
- if (post.dataset.sbtn === '1') return;
- let btnParent = post.querySelector('div.xqcrz7y.x78zum5.x1qx5ct2') ||
- post.closest('div.xqcrz7y.x78zum5.x1qx5ct2');
- if (!btnParent) return;
- post.dataset.sbtn = '1';
- btnParent.style.position = 'relative';
- const btn = document.createElement('div');
- btn.textContent = '📸';
- btn.title = '截圖社團貼文';
- Object.assign(btn.style, {
- position: 'absolute', left: '-40px', top: '0',
- width: '32px', height: '32px', display: 'flex',
- alignItems: 'center', justifyContent: 'center',
- borderRadius: '50%', backgroundColor: '#3A3B3C',
- color: 'white', cursor: 'pointer', zIndex: '9999',
- transition: 'background .2s',
- });
- btn.addEventListener('mouseenter', () => btn.style.backgroundColor = '#4E4F50');
- btn.addEventListener('mouseleave', () => btn.style.backgroundColor = '#3A3B3C');
- btn.addEventListener('click', async e => {
- e.stopPropagation();
- btn.textContent = '⏳'; btn.style.pointerEvents = 'none';
- try {
- post.querySelectorAll('span,a,div,button').forEach(el => {
- const txt = el.innerText?.trim() || el.textContent?.trim();
- if (['查看更多', '顯示更多', 'See more', 'See More', '…更多'].includes(txt)) {
- el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
- }
- });
- await new Promise(r => setTimeout(r, 1000));
- await new Promise(r => setTimeout(r, 500));
- const groupId = location.pathname.match(/^\/groups\/(\d+)/)?.[1] || 'unknownGroup';
- const now = new Date();
- const pad = n => n.toString().padStart(2, '0');
- const ts = `${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}_${pad(now.getHours())}_${pad(now.getMinutes())}_${pad(now.getSeconds())}`;
- const filename = `${groupId}_${ts}.png`;
- // 等待字型載入
- await document.fonts.ready;
- // 執行截圖
- const dataUrl = await window.htmlToImage.toPng(post, {
- backgroundColor: '#1c1c1d',
- pixelRatio: 2,
- cacheBust: true,
- });
- // 儲存圖片
- const link = document.createElement('a');
- link.href = dataUrl;
- link.download = filename;
- link.click();
- btn.textContent = '✅';
- } catch (err) {
- console.error('社團截圖錯誤:', err);
- alert('截圖失敗,請稍後再試');
- btn.textContent = '❌';
- }
- setTimeout(() => { btn.textContent = '📸'; btn.style.pointerEvents = 'auto'; }, 1000);
- });
- btnParent.appendChild(btn);
- });
- });
- groupObserver.observe(document.body, { childList: true, subtree: true });
- console.log('[腳本] 社團觀察器已啟動');
- }
- function stopGroupPostObserver() {
- if (groupObserver) {
- groupObserver.disconnect(); groupObserver = null;
- console.log('[腳本] 社團觀察器已停止');
- }
- }
- // ====== 粉絲專頁截圖按鈕功能 ======
- let pageObserver = null;
- function initPagePostObserver() {
- if (pageObserver) return;
- pageObserver = new MutationObserver(() => {
- document.querySelectorAll('div.x1yztbdb.x1n2onr6.xh8yej3.x1ja2u2z').forEach(post => {
- if (post.dataset.sbtn === '1') return;
- let btnParent = post.querySelector('div.xqcrz7y.x78zum5.x1qx5ct2.x1y1aw1k.xf159sx.xwib8y2.xmzvs34.xw4jnvo') ||
- post.closest('div.xqcrz7y.x78zum5.x1qx5ct2.x1y1aw1k.xf159sx.xwib8y2.xmzvs34.xw4jnvo');
- if (!btnParent) return;
- post.dataset.sbtn = '1';
- btnParent.style.position = 'relative';
- const btn = document.createElement('div');
- btn.textContent = '📸';
- btn.title = '截圖粉專貼文';
- Object.assign(btn.style, {
- position: 'absolute', left: '-40px', top: '0',
- width: '32px', height: '32px', display: 'flex',
- alignItems: 'center', justifyContent: 'center',
- borderRadius: '50%', backgroundColor: '#3A3B3C',
- color: 'white', cursor: 'pointer', zIndex: '9999',
- transition: 'background .2s',
- });
- btn.addEventListener('mouseenter', () => btn.style.backgroundColor = '#4E4F50');
- btn.addEventListener('mouseleave', () => btn.style.backgroundColor = '#3A3B3C');
- btn.addEventListener('click', async e => {
- e.stopPropagation();
- btn.textContent = '⏳'; btn.style.pointerEvents = 'none';
- try {
- post.querySelectorAll('span,a,div,button').forEach(el => {
- const txt = el.innerText?.trim() || el.textContent?.trim();
- if (['查看更多', '顯示更多', 'See more', 'See More', '…更多'].includes(txt)) {
- el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
- }
- });
- await new Promise(r => setTimeout(r, 1000));
- await new Promise(r => setTimeout(r, 500));
- const now = new Date();
- const pad = n => n.toString().padStart(2, '0');
- const ts = `${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}_${pad(now.getHours())}_${pad(now.getMinutes())}_${pad(now.getSeconds())}`;
- const pageName = location.pathname.split('/').filter(Boolean)[0] || 'page';
- const filename = `${pageName}_${ts}.png`;
- // 等待字型載入
- await document.fonts.ready;
- // 執行截圖
- const dataUrl = await window.htmlToImage.toPng(post, {
- backgroundColor: '#1c1c1d',
- pixelRatio: 2,
- cacheBust: true,
- });
- // 儲存圖片
- const link = document.createElement('a');
- link.href = dataUrl;
- link.download = filename;
- link.click();
- btn.textContent = '✅';
- } catch (err) {
- console.error('粉專截圖錯誤:', err);
- alert('截圖失敗,請稍後再試');
- btn.textContent = '❌';
- }
- setTimeout(() => { btn.textContent = '📸'; btn.style.pointerEvents = 'auto'; }, 1000);
- });
- btnParent.appendChild(btn);
- });
- });
- pageObserver.observe(document.body, { childList: true, subtree: true });
- console.log('[腳本] 粉專觀察器已啟動');
- }
- function stopPagePostObserver() {
- if (pageObserver) {
- pageObserver.disconnect();
- pageObserver = null;
- console.log('[腳本] 粉專觀察器已停止');
- }
- }
- let lastPathname = location.pathname;
- // 判斷是否為社團頁面
- function isGroupPage(path) {
- return path.startsWith('/groups/');
- }
- // 判斷是否為粉專頁面(排除常見非粉專頁)
- function isPagePage(path) {
- if (isGroupPage(path)) return false;
- const segments = path.split('/').filter(Boolean);
- if (segments.length === 0) return false;
- // 排除非粉專常見路徑,例如 marketplace、gaming、watch 等
- const excluded = ['watch', 'gaming', 'marketplace', 'groups', 'friends', 'notifications', 'messages'];
- return !excluded.includes(segments[0]);
- }
- // 根據路徑切換對應的觀察器
- function handlePathChange(newPath) {
- stopGroupPostObserver();
- stopPagePostObserver();
- if (isGroupPage(newPath)) {
- initGroupPostObserver();
- } else if (isPagePage(newPath)) {
- initPagePostObserver();
- } else {
- console.log(`[腳本] 非支援的頁面類型:${newPath}`);
- }
- }
- // 初次執行(初次載入)
- handlePathChange(location.pathname);
- // 每秒偵測路徑變化並切換觀察器
- setInterval(() => {
- const currentPath = location.pathname;
- if (currentPath !== lastPathname) {
- lastPathname = currentPath;
- handlePathChange(currentPath);
- }
- }, 1000);
- })();