Facebook All Comments Helper

Easy way to show all comments.


Install this script?
// ==UserScript==// @name         Facebook All Comments Helper// @name:zh-TW   FB全部留言小幫手// @name:zh-CN   FB全部留言小帮手// @namespace    https://github.com/Xuitty/FBallComments// @version      2.4// @description  Easy way to show all comments.// @description:zh-tw  讓您更快打開全部留言// @description:zh-cn  让您更快打开全部留言// @author       Xuitty// @match        https://www.facebook.com/*// @icon         https://www.google.com/s2/favicons?sz=64&domain=facebook.com// @grant        GM_registerMenuCommand// @grant        GM_setValue// @grant        GM_getValue// @grant        GM.registerMenuCommand// @grant        GM.setValue// @grant        GM.getValue// @run-at       document-end// @license      MIT// ==/UserScript==/*** string array for detecting the menu button*/const langs = {de: ["Relevanteste", "Top-Kommentare", "Am zutreffendsten", "Neueste zuerst", "Neueste", "Alle Kommentare"],en: ["Top comments", "Most relevant", "Most applicable", "Most recent", "Newest", "All comments"],es: ["Comentarios destacados", "Más relevantes", "Más pertinentes", "Más recientes", "Más recientes", "Todos los comentarios"],hu: ["A legfontosabb hozzászólások", "A legrelevánsabbak", "A témához leginkább illők", "A legújabbak", "A legutóbbiak", "Az összes hozzászólás"],ja: ["トップコメント", "関連度の高い順", "最も適切", "新しい順", "新しい順", "すべてのコメント"],ko: ["관련성 높은 댓글", "참여도 높은 댓글", "적합성 높은 순", "최신순", "날짜 내림차순", "모든 댓글"],fr: ["Plus pertinents", "Les meilleurs commentaires", "Les plus pertinents", "Plus récents", "Les plus récents", "Tous les commentaires"],sk: ["Top komentáre", "Najrelevantnejšie", "Najvhodnejšie", "Najnovšie", "Najnovšie", "Všetky komentáre"],sl: ["Najbolj priljubljeni komentarji", "Najustreznejši", "Najustreznejše", "Najnovejši", "Najnovejši", "Vsi komentarji"],"zh-Hans": ["热门评论", "最相关", "最合适", "从新到旧", "最新", "所有评论"],"zh-Hant": ["最熱門留言", "最相關", "最相關", "最新", "由新到舊", "所有留言"],};/*** string array for notification*/const notificationStr = {de: ["Wechseln zu allen Kommentaren!", "Wechseln zu den neuesten Kommentaren!"],en: ["Switch to All Comments!", "Switch to Latest Comments!"],es: ["Cambiar a todos los comentarios!", "Cambiar a los comentarios más recientes!"],hu: ["Váltás az összes hozzászólásra!", "Váltás a legújabb hozzászólásokra!"],ja: ["すべてのコメントに切り替え!", "最新のコメントに切り替え!"],ko: ["모든 댓글로 전환!", "최신 댓글로 전환!"],fr: ["Passer à tous les commentaires!", "Passer aux commentaires les plus récents!"],sk: ["Prepnúť na všetky komentáre!", "Prepnúť na najnovšie komentáre!"],sl: ["Preklopite na vse komentarje!", "Preklopite na najnovejše komentarje!"],"zh-Hant": ["切換到所有留言!", "切換到最新留言!"],"zh-Hans": ["切换到所有评论!", "切换到最新评论!"],};/*** string array for settings*/const settingsStr = {"zh-Hant": {settings: "設定",autoDetect: "自動切換為全部/最新留言",notifyEnabled: "操作通知",isAll: "全部留言/最新留言",isScroll: "是否開啟捲動",scrollBehavior: "捲動特效",hideSettings: "隱藏選單",smooth: "平滑",auto: "無",openMenu: "開啟選單",needRefresh: "需要重新整理頁面應用自動切換",},"zh-Hans": {settings: "设置",autoDetect: "自动切换为全部/最新评论",notifyEnabled: "操作通知",isAll: "全部评论/最新评论",isScroll: "是否开启滚动",scrollBehavior: "滚动特效",hideSettings: "隐藏菜单",smooth: "平滑",auto: "无",openMenu: "打开菜单",needRefresh: "需要重新刷新页面应用自动切换",},en: {settings: "Settings",autoDetect: "Auto detect all/latest comments",notifyEnabled: "Notify after action",isAll: "All comments/Latest comments",isScroll: "Enable scroll effect",scrollBehavior: "Scroll behavior",hideSettings: "Hide settings",smooth: "Smooth",auto: "None",openMenu: "Open menu",needRefresh: "Need to refresh the page to apply auto detection",},de: {settings: "Einstellungen",autoDetect: "Automatisch alle/neuesten Kommentare erkennen",notifyEnabled: "Nach Aktion benachrichtigen",isAll: "Alle Kommentare/Neueste Kommentare",isScroll: "Bildlauf aktivieren",scrollBehavior: "Bildlaufverhalten",hideSettings: "Einstellungen ausblenden",smooth: "Sanft",auto: "Keine",openMenu: "Menü öffnen",needRefresh: "Die Seite muss aktualisiert werden, um die automatische Erkennung anzuwenden",},es: {settings: "Ajustes",autoDetect: "Detectar automáticamente todos/los comentarios más recientes",notifyEnabled: "Notificar después de la acción",isAll: "Todos los comentarios/Comentarios más recientes",isScroll: "Activar efecto de desplazamiento",scrollBehavior: "Comportamiento de desplazamiento",hideSettings: "Ocultar ajustes",smooth: "Suave",auto: "Ninguno",openMenu: "Abrir menú",needRefresh: "Necesita actualizar la página para aplicar la detección automática",},fr: {settings: "Paramètres",autoDetect: "Détecter automatiquement tous/les derniers commentaires",notifyEnabled: "Notifier après l'action",isAll: "Tous les commentaires/Derniers commentaires",isScroll: "Activer l'effet de défilement",scrollBehavior: "Comportement de défilement",hideSettings: "Masquer les paramètres",smooth: "Doux",auto: "Aucun",openMenu: "Ouvrir le menu",needRefresh: "Besoin de rafraîchir la page pour appliquer la détection automatique",},hu: {settings: "Beállítások",autoDetect: "Az összes/legújabb hozzászólás automatikus észlelése",notifyEnabled: "Értesítés az akció után",isAll: "Minden megjegyzés/Legfrissebb megjegyzések",isScroll: "Gördítési hatás engedélyezése",scrollBehavior: "Gördülési viselkedés",hideSettings: "Beállítások elrejtése",smooth: "Simít",auto: "Nincs",openMenu: "Menü megnyitása",needRefresh: "Az automatikus észlelés alkalmazásához frissíteni kell az oldalt",},ja: {settings: "設定",autoDetect: "すべて/最新のコメントを自動検出",notifyEnabled: "アクション後に通知",isAll: "すべてのコメント/最新のコメント",isScroll: "スクロール効果を有効にする",scrollBehavior: "スクロール動作",hideSettings: "設定を非表示",smooth: "スムーズ",auto: "なし",openMenu: "メニューを開く",needRefresh: "自動検出を適用するにはページを更新する必要があります",},ko: {settings: "설정",autoDetect: "모든/최신 댓글 자동 감지",notifyEnabled: "작업 후 알림",isAll: "모든 댓글/최신 댓글",isScroll: "스크롤 효과 사용",scrollBehavior: "스크롤 동작",hideSettings: "설정 숨기기",smooth: "부드러운",auto: "없음",openMenu: "메뉴 열기",needRefresh: "자동 감지를 적용하려면 페이지를 새로 고쳐야 합니다",},sk: {settings: "Nastavenia",autoDetect: "Automaticky zistiť všetky/najnovšie komentáre",notifyEnabled: "Upozorniť po akcii",isAll: "Všetky komentáre/Najnovšie komentáre",isScroll: "Povoliť posuvný efekt",scrollBehavior: "Správanie posuvu",hideSettings: "Skryť nastavenia",smooth: "Hladký",auto: "Žiadny",openMenu: "Otvoriť menu",needRefresh: "Na použitie automatického zistenia je potrebné obnoviť stránku",},sl: {settings: "Nastavitve",autoDetect: "Samodejno zaznani vsi/najnovejši komentarji",notifyEnabled: "Obvesti po dejanju",isAll: "Vsi komentarji/Najnovejši komentarji",isScroll: "Omogoči učinek drsenja",scrollBehavior: "Vedenje drsenja",hideSettings: "Skrij nastavitve",smooth: "Gladko",auto: "Brez",openMenu: "Odpri meni",needRefresh: "Za uporabo samodejnega zaznavanja je treba osvežiti stran",},};/*** get the language of the fb* @returns the language of the fb*/function detectLang() {return document.getElementById("facebook")?.getAttribute("lang") || "en";}/*** get the settings string array* @returns the settings string array*/function getSettingsStr() {return settingsStr[detectLang()] || settingsStr.en;}/*** get the xpath for menu* @returns the xpath for menu*/function getMenuButtonXPath() {const lang = langs[detectLang()] || langs.en;return `//span[not(@style) and (text()='${lang[0]}' or text()='${lang[1]}' or text()='${lang[2]}' or text()='${lang[3]}' or text()='${lang[4]}' or text()='${lang[5]}')]`;}/*** handle the click the comment button or the right bottom comment count*/function handleClickOutside() {if (settings.isAll) showAllComment();else showLatestComment();}/*** wait for the element to appear in the DOM* @param {string} xpath* @param {Function} callback* @param {number} timeout* @param {number} interval*/function waitForElement(xpath, callback, timeout = 3000, intervalTime = 100) {const startTime = Date.now();const interval = setInterval(() => {const element = document.evaluate(xpath, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;if (element) {clearInterval(interval);callback(element);} else if (Date.now() - startTime > timeout) {clearInterval(interval);console.warn("Timeout: Element not found for XPath", xpath);}}, intervalTime);}/**** @param {Element} element*/function safeClick(element) {try {element.click();} catch (err) {console.error("Error clicking element:", err);}}/*** show the notification to user after the action* override is for mode in settings is allcomments and user press ctrl+dblclick/ctrl+insert* @param {boolean} reverse*/async function notifyUser(reverse = false) {const notification = document.createElement("div");notification.setAttribute("id", "FBAllCommentsHelperNotification");notification.style.position = "fixed";notification.style.bottom = "20px";notification.style.left = "20px";notification.style.backgroundColor = "rgba(0,0,0,1)";notification.style.color = "white";notification.style.padding = "10px";notification.style.borderRadius = "5px";notification.style.zIndex = "9999";const notifyingTimeout = Number(await GM.getValue("notifyingTimeout"));if (reverse) {notification.textContent = notificationStr[detectLang()][settings.isAll ? 1 : 0] || notificationStr.en[settings.isAll ? 1 : 0];} else {notification.textContent = notificationStr[detectLang()][settings.isAll ? 0 : 1] || notificationStr.en[settings.isAll ? 0 : 1];}document.body.appendChild(notification);if (notifyingTimeout) {document.getElementById("FBAllCommentsHelperNotification").remove();clearTimeout(notifyingTimeout);await GM.setValue("notifyingTimeout", undefined);}const id = setTimeout(async () => {notification.remove();await GM.setValue("notifyingTimeout", undefined);}, 3000);await GM.setValue("notifyingTimeout", id);}/*** parse the user action* @param {*} e* @returns*/function actionParser(e) {if (e.type === "dblclick" && e.ctrlKey) {!settings.isAll ? showAllComment(true) : showLatestComment(true);return;}if (e.type === "dblclick") {settings.isAll ? showAllComment() : showLatestComment();return;}if (e.code === "Insert" && e.ctrlKey) {!settings.isAll ? showAllComment(true) : showLatestComment(true);return;}if (e.code === "Insert") {settings.isAll ? showAllComment() : showLatestComment();return;}}/*** detecting the changes in the DOM* @param {Function} callback*/function observeDOM(callback) {const observer = new MutationObserver((mutations) => {mutations.forEach(() => callback());});observer.observe(document.body, { childList: true, subtree: true });}/*** show all comments*/function showAllComment(reverse = false) {waitForElement(//getting the menugetMenuButtonXPath(),(element) => {if (settings.isScroll) {element.scrollIntoView({ behavior: settings.scrollBehavior, block: "center" });}setTimeout(() => {safeClick(element);}, 100);waitForElement(//getting the items in menu"//*[@role='menuitem']",(element) => {const menuItems = document.querySelectorAll('*[role="menuitem"]');if (menuItems.length > 1) {safeClick(menuItems[menuItems.length - 1]);if (settings.notifyEnabled) {notifyUser(reverse);}}});});}/*** show latest comment* override is for mode in settings is allcomments and user press ctrl+dblclick/ctrl+insert* @param {boolean} reverse*/function showLatestComment(reverse = false) {waitForElement(//getting the menugetMenuButtonXPath(),(element) => {if (settings.isScroll) {element.scrollIntoView({ behavior: settings.scrollBehavior, block: "center" });}setTimeout(() => {safeClick(element);}, 100);waitForElement(//getting the items in menu"//*[@role='menuitem']",(element) => {const menuItems = document.querySelectorAll('*[role="menuitem"]');if (menuItems.length > 1) {safeClick(menuItems[menuItems.length - 2]);if (settings.notifyEnabled) {notifyUser(reverse);}}});});}/*** bind the event for detected object after the DOM changes*/function bindForDetected(action = "bind") {let commentRightBottomBtn = document.querySelectorAll("div[role='button'][tabindex='0'][id^=':']");let commentBtn = document.querySelectorAll("span[data-ad-rendering-role='comment_button']");commentRightBottomBtn.forEach((btn) => {if (action === "remove") {btn.removeEventListener("click", handleClickOutside);} else {btn.addEventListener("click", handleClickOutside);}});commentBtn.forEach((btn) => {if (action === "remove") {btn.parentElement.parentElement.parentElement.removeEventListener("click", handleClickOutside);} else {btn.parentElement.parentElement.parentElement.addEventListener("click", handleClickOutside);}});}/*** default settings*/const settings = {autoDetect: true, // auto detect all/latest commentsscrollBehavior: "smooth", // scroll behaviornotifyEnabled: true, // notify after actionisAll: true, // all comments/latest commentsisHidden: false, // settings panel hiddenisScroll: false, // enable scroll effect};/*** save settings to gm storage*/async function saveSettings() {await GM.setValue("fbAllCommentsHelperSettings", JSON.stringify(settings));}/*** load settings from gm storage*/async function loadSettings() {const storedSettings = await GM.getValue("fbAllCommentsHelperSettings");if (storedSettings) {Object.assign(settings, JSON.parse(storedSettings));}}/*** create settings panel*/function createSettingsPanel() {const panel = document.createElement("div");panel.id = "settingsPanel";panel.style.position = "fixed";panel.style.top = "10px";panel.style.right = "10px";panel.style.backgroundColor = "rgba(0, 0, 0, 1)";panel.style.padding = "10px";panel.style.borderRadius = "5px";panel.style.zIndex = "9999";panel.style.fontSize = "14px";panel.innerHTML = `<h4 style="margin: 0 0 10px;color: white;">${getSettingsStr().settings}</h4><label style="color: white;"><input type="checkbox" id="autoDetect" ${settings.autoDetect ? "checked" : ""}>${getSettingsStr().autoDetect}</label><br><label style="color: white;"><input type="checkbox" id="notifyEnabled" ${settings.notifyEnabled ? "checked" : ""}>${getSettingsStr().notifyEnabled}</label><br><label style="color: white;"><input type="checkbox" id="isAll" ${settings.isAll ? "checked" : ""}>${getSettingsStr().isAll}</label><br><label style="color: white;"><input type="checkbox" id="isScroll" ${settings.isScroll ? "checked" : ""}>${getSettingsStr().isScroll}</label><br><label style="color: white;">${getSettingsStr().scrollBehavior}<select id="scrollBehavior"><option value="smooth" ${settings.scrollBehavior === "smooth" ? "selected" : ""}>${getSettingsStr().smooth}</option><option value="auto" ${settings.scrollBehavior === "auto" ? "selected" : ""}>${getSettingsStr().auto}</option></select></label><br><button id="hideSettings" style="margin-top: 10px;">${getSettingsStr().hideSettings}</button>`;document.body.appendChild(panel);// add panel buttons event listenersdocument.getElementById("autoDetect").addEventListener("change", (e) => {settings.autoDetect = e.target.checked;saveSettings();alert(getSettingsStr().needRefresh);});document.getElementById("notifyEnabled").addEventListener("change", (e) => {settings.notifyEnabled = e.target.checked;saveSettings();});document.getElementById("isAll").addEventListener("change", (e) => {settings.isAll = e.target.checked;saveSettings();});document.getElementById("isScroll").addEventListener("change", (e) => {settings.isScroll = e.target.checked;saveSettings();});document.getElementById("scrollBehavior").addEventListener("change", (e) => {settings.scrollBehavior = e.target.value;saveSettings();});document.getElementById("hideSettings").addEventListener("click", () => {settings.isHidden = true;document.getElementById("settingsPanel").remove();saveSettings();});}(async function () {"use strict";window.addEventListener("load", async () => {await loadSettings();await saveSettings();await GM_registerMenuCommand(getSettingsStr().openMenu, () => {// gm menu commandcreateSettingsPanel();settings.isHidden = false;});if (!settings.isHidden) {// create settings panel when isHidden is falsecreateSettingsPanel();}document.addEventListener("dblclick", actionParser); //handle dblclick eventdocument.addEventListener("keydown", actionParser); //handle keydown eventbindForDetected("remove");if (settings.autoDetect) {bindForDetected();// for auto detectobserveDOM(bindForDetected);}});})();