您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Linuxdo auto read
// ==UserScript== // @name Auto Read bee // @namespace http://tampermonkey.net/ // @version 1.2.3 // @description Linuxdo auto read // @author bee // @match https://meta.discourse.org/* // @match https://linux.do/* // @match https://meta.appinn.net/* // @match https://community.openai.com/ // @grant none // @license MIT // @icon https://www.google.com/s2/favicons?domain=linux.do // ==/UserScript== const readSuggestPost = false; const postCommentsCount = 200; // filter post under this comments, over this will PASS const BASE_URL = `https://${window.location.hostname}`; const refreshRate = { normal: localStorage.getItem("refreshRateNormal") || 2400, blueDot: localStorage.getItem("refreshRateBlueDot") || 300, }; /** * This function set Floor infos value, return how much post been read. * @param {boolean} refresh - if true, reset all values * @returns {Object} - Returns an object with the result of the operation and the number of posts read. */ let floorStatus = { startFloor: 0, endFloor: 0, lastStartFloor: 0, lastEndFloor: 0, readCounter: parseInt(localStorage.getItem("readCounter") || "0", 10), failCounter: 0, rebootUrl: null, }; let directWaitCounter = 0; let readInterval; let waitNextRefresh = 0; function autoRunStart(singleTopic = false) { let finalRate; if (localStorage.getItem("blueDotMode") === "true") { finalRate = localStorage.getItem("refreshRateBlueDot") || refreshRate.blueDot; } else { finalRate = localStorage.getItem("refreshRateNormal") || refreshRate.normal; if (finalRate < 2000) { finalRate += 2000; } } readInterval = setInterval(async () => { if ((!singleTopic && check_read_limit()) || !waitReady()) { return; } let errorElement = document.querySelector(".page-not-found"); if (errorElement) { addDebugTextarea("[WARN] Page not found"); await directNextPost(); return; } let retryElement = document.querySelector(".btn.btn-icon-text.btn-primary.topic-retry"); let retryElement2 = document.querySelector(".error-page"); if (retryElement) { retryElement.click(); return; } if (retryElement2) { document.querySelector(".btn.btn-icon-text.btn-primary").click(); } if (floorStatus.rebootUrl) { gotoUrl(floorStatus.rebootUrl); floorStatus.rebootUrl = null; return; } if (window.location.href.includes("/t/topic/")) { const isNew = isNewPost(); if (isNew) { addDebugTextarea("[INFO] New post"); discourseDo("lastTimeReadProcess"); setFloor(true); } if (localStorage.getItem("blueDotMode") === "false") { checkLastRead(); } setBtnText_readToday(); const existFloor = setFloor(); // no article found if (existFloor.result === "fail" || existFloor.result === "EOF") { if (singleTopic) { document.getElementById("btnAutoRead").click(); return; } await directNextPost(); addDebugTextarea("[INFO] Direct post..."); return; } if (existFloor.result === "noBlueDot") { scrollIntoBee(existFloor.targetElement); return; } let failRetry = localStorage.getItem("reloadWaitTime") || 25; if (floorStatus.failCounter < (failRetry / finalRate) * 500 && existFloor.result === "success" && !isInViewport(existFloor.targetElement)) { scrollIntoBee(existFloor.targetElement); return; } if (floorStatus.failCounter < (failRetry / finalRate) * 500 && existFloor.result === "success") { addDebugTextarea(`[INFO] wait for blue dot #${existFloor.targetElement.id}...`); return; } if (floorStatus.failCounter < (failRetry / finalRate) * 1000 && existFloor.result === "success") { /*addDebugTextarea("[INFO] +800..."); window.scrollBy(0, 800);*/ addDebugTextarea("[INFO] wait for blue dot...50%..."); return; } if (floorStatus.failCounter >= (failRetry / finalRate) * 1000 && existFloor.result === "success") { // refresh addDebugTextarea("[INFO] blue dot stuck, refresh..."); floorStatus.failCounter = 0; //window.location.reload(); floorStatus.rebootUrl = window.location.href; gotoUrl(BASE_URL); } //await directNextPost(); return; } // not in topic page if (singleTopic) { document.getElementById("btnAutoRead").click(); return; } await directNextPost(); }, finalRate); } function setFloor(refresh = false) { if (refresh) { floorStatus = { startFloor: 0, endFloor: 0, lastStartFloor: 0, lastEndFloor: 0, readCounter: parseInt(localStorage.getItem("readCounter") || "0", 10), failCounter: 0, }; return {result: "refreshed"}; } let element_floor = document.querySelectorAll(".boxed.onscreen-post"); if (!element_floor) { return {result: "fail"}; } // Find which element.id === post_${postFloor} let targetElement = null; let secondOutOfView = false; for (let counterTemp = 0; element_floor.length > counterTemp; counterTemp++) { // if blueDotMode is true, find the first blue dot if (localStorage.getItem("blueDotMode") === "true" && element_floor[counterTemp].querySelector(".read-state:not(.read)")) { targetElement = element_floor[counterTemp]; //addDebugTextarea(`found => #${element_floor[counterTemp].id}`); break; } if (secondOutOfView === true && localStorage.getItem("blueDotMode") === "false" && isInViewport(element_floor[counterTemp]) === false) { targetElement = element_floor[counterTemp]; break; } if (localStorage.getItem("blueDotMode") === "false" && isInViewport(element_floor[counterTemp]) === true) { secondOutOfView = true; } } let floorNunsFromBar = document .querySelector(".timeline-replies") .textContent.trim() .split(" / "); if (floorNunsFromBar && (parseInt(floorNunsFromBar[1]) - parseInt(floorNunsFromBar[0]) < 5) && targetElement === null) { return {result: "EOF"}; } if (targetElement === null) { return { result: "noBlueDot", targetElement: element_floor[element_floor.length - 1], }; } floorStatus.lastStartFloor = floorStatus.startFloor; floorStatus.lastEndFloor = floorStatus.endFloor; floorStatus.startFloor = parseInt(targetElement.id.replace("post_", "")); floorStatus.endFloor = parseInt(element_floor[element_floor.length - 1].id.replace("post_", "")); // get readCounter from storage floorStatus.readCounter = parseInt(localStorage.getItem("readCounter") || "0", 10); floorStatus.readCounter += floorStatus.startFloor - floorStatus.lastStartFloor > 10 ? 0 : floorStatus.startFloor - floorStatus.lastStartFloor; localStorage.setItem("readCounter", floorStatus.readCounter.toString()); if (floorStatus.startFloor - floorStatus.lastStartFloor === 0) { floorStatus.failCounter++; } else { floorStatus.failCounter = 0; } return { result: "success", targetElement: targetElement, readFloor: floorStatus.readCounter, }; } // back to last read if exist /** * This function checks if there is a 'last read' button on the page and clicks it if it exists. * The 'last read' button is typically used in forums to navigate to the last read post in a thread. * * @returns {boolean} - Returns true if the 'last read' button was found and clicked, false otherwise. */ function checkLastRead() { let buttonLastRead = document.querySelector(".timeline-last-read .btn"); if (buttonLastRead) { buttonLastRead.click(); return true; } return false; } function addDebugTextarea(debugText = "") { if (debugText === "" && !document.getElementById("debugTextarea")) { let textarea = document.createElement("textarea"); textarea.id = "debugTextarea"; textarea.style.cssText = ` position: fixed; top: 4rem; right: 0; width: 300px; height: 600px; z-index: 1000; background: rgba(0, 0 ,0, 0.8); border: 1px solid #ccc; border-radius: 4px; padding: 4px; resize: none; `; document.body.appendChild(textarea); } else if (localStorage.getItem("debugPanel") === "true" && document.getElementById("debugTextarea")) { const textarea = document.getElementById("debugTextarea"); const lines = textarea.value.split("\n"); const lastLine = lines[lines.length - 1]; // 最後一行的內容 const match = lastLine.match(/(.*?)(\s\(\d+\))?$/); const lastText = match[1]; const count = match[2] ? parseInt(match[2].match(/\d+/)[0]) : 1; if (lastText === debugText) { lines[lines.length - 1] = `${lastText} (${count + 1})`; } else { lines.push(debugText); } textarea.value = lines.join("\n"); textarea.scrollTop = textarea.scrollHeight; } } function setBtnText_readToday() { let btnAutoRead = document.getElementById("btnAutoRead"); btnAutoRead.textContent = localStorage.getItem("read") === "true" ? `◼ ( ${floorStatus.readCounter} )` : `▶ ( ${floorStatus.readCounter} )`; } function check_read_limit() { if (floorStatus.readCounter > (localStorage.getItem("stopLimit") || 250)) { document.getElementById("btnAutoRead").click(); return true; } return false; } function isInViewport(element) { const rect = element.getBoundingClientRect(); return (rect.top >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) //rect.left >= 0 && //rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && //rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } async function directNextPost() { if (waitNextRefresh === 0) { waitNextRefresh++; return; } waitNextRefresh = 0; if (window.location.href.includes("/t/topic/") && readSuggestPost) { let suggestPost; suggestPost = document.querySelector('a[href="/unread"]'); if (suggestPost) { suggestPost.click(); addDebugTextarea("[INFO] found unread ==> ○"); return; } addDebugTextarea("[WARN] unread post ==> X"); } if (window.location.href.includes("/search") /* && directWaitCounter < 5*/) { let links = document.querySelectorAll('a[href^="/t/topic"]'); let hrefs; if (links && 0 < links.length) { directWaitCounter = 0; //hrefs = Array.from(links, link => link.getAttribute('href')); hrefs = Array.from(links, (link) => link.getAttribute("href").replace(/(\/t\/topic\/\d+).*/, "$1")); const tempUrl = hrefs.shift(); localStorage.setItem("unreadList", JSON.stringify(hrefs)); addDebugTextarea("[INFO] Got ", hrefs.length, " unread posts"); gotoUrl(tempUrl); return; } directWaitCounter++; addDebugTextarea("[WARN] Got 0 unread posts"); } /*if (window.location.href.includes("/search")) { directWaitCounter = 0; gotoUrl("/ubm"); //useBackupMethod }*/ if (window.location.href === `${BASE_URL}/unread` /*|| window.location.href === `${BASE_URL}/`*/) { let suggestPost = document.querySelector('a[class*="badge badge-notification"]'); if (suggestPost) { addDebugTextarea("[INFO] found post ==> ○"); suggestPost.click(); } return; } let unreadList = JSON.parse(localStorage.getItem("unreadList")); if (!unreadList || unreadList.length <= 0) { gotoUrl("/search?expanded=true&q=in%3Aunseen%20min_posts%3A20"); return; } if (0 < unreadList.length) { let tempUrl = unreadList.shift(); let userUrl = window.location.href.match(/\/t\/topic\/(\d+)?/); if (userUrl !== null && userUrl[1] === tempUrl.match(/\/t\/topic\/(\d+)?/)[1]) { localStorage.setItem("unreadList", JSON.stringify(unreadList)); tempUrl = unreadList.shift(); addDebugTextarea("[INFO] Delete done post"); } addDebugTextarea(`[INFO] Least ${unreadList.length} unread posts in storage`); if (tempUrl) { gotoUrl(tempUrl.replace(/(\/t\/topic\/\d+).*/, "$1")); } return; } // backup method /*addDebugTextarea("[WARN] Use api to get unread posts"); const topicListStr = localStorage.getItem("topicList"); if (!topicListStr) { await getLatestTopic(); } const topicList = JSON.parse(topicListStr); if (topicList && 0 < topicList.length) { // 从未读列表中取出第一个 let topic = topicList.shift(); if (window.location.href.includes(`/t/topic/${topic.id}`)) { localStorage.setItem("topicList", JSON.stringify(topicList)); topic = topicList.shift(); } //window.location.href = `${BASE_URL}/t/topic/${topic.id}`; gotoUrl(`/t/topic/${topic.id}`); }*/ } function gotoUrl(url) { let directPost = document.querySelector('a[href="/"]'); directPost.href = `${url}`; directPost.click(); directPost.href = "/"; } function scrollIntoBee(element, marginOffset = 4) { const temp_marginTop = element.style.marginTop === undefined || "" ? "0px" : element.style.marginTop; //decrease 130px from temp_marginTop element.style.marginTop = `-${marginOffset}rem`; element.scrollIntoView({behavior: "smooth"}); element.style.marginTop = temp_marginTop; if (element.id) { addDebugTextarea(`[INFO] Read => #${element.id}`); } } function discourseDo(action) { let eventFinal; switch (action) { case "lastTimeReadProcess": eventFinal = new KeyboardEvent("keydown", { key: "l", code: "KeyL", keyCode: 76, which: 76, shiftKey: true, bubbles: true, cancelable: true, }); break; case "nextPost": eventFinal = new KeyboardEvent("keydown", { key: "j", code: "KeyJ", keyCode: 74, which: 74, shiftKey: true, bubbles: true, cancelable: true, }); break; default: console.warn(`[ERROR] Unknown action: ${action}`); return; } document.body.dispatchEvent(eventFinal); } function isNewPost() { if (localStorage.getItem("readUrl").split("/").slice(0, 6).join("/") !== window.location.href.split("/").slice(0, 6).join("/")) { localStorage.setItem("readUrl", window.location.href.split("/").slice(0, 6).join("/")); return true; } return false; } function waitReady() { let element = document.querySelector(".loading-indicator-container"); return element && element.classList.contains("ready"); } function addStartBtn() { //let ul = document.querySelector('.icons.d-header-icons');btn btn-icon-text btn-default sidebar__panel-switch-button let ul = document.querySelector(".sidebar-footer-wrapper"); let li = document.createElement("li"); li.className = "sidebar-section-link-wrapper"; let a = document.createElement("a"); a.id = "startA"; a.className = "ember-view sidebar-section-link sidebar-row"; //let a2 = document.createElement('a'); //a2.id = "directUrl"; //a2.className = "ember-view sidebar-section-link sidebar-row"; //a2.href = ""; let span1 = document.createElement("span"); span1.className = "sidebar-section-link-prefix icon"; let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.classList.add("fa", "d-icon", "d-icon-angle-right", "svg-icon", "prefix-icon", "svg-string"); /* let use = document.createElementNS("http://www.w3.org/2000/svg", "use"); use.setAttributeNS( "http://www.w3.org/1999/xlink", "xlink:href", "#angle-right" );*/ let span2 = document.createElement("span"); span2.className = "sidebar-section-link-content-text"; span2.id = "btnAutoRead"; span2.textContent = "▶ ( - )"; //svg.appendChild(use); span1.appendChild(svg); a.appendChild(span1); a.appendChild(span2); //li.appendChild(a2); li.appendChild(a); ul.prepend(li); a.onclick = function () { const currentlyReading = localStorage.getItem("read") === "true"; const newReadState = !currentlyReading; localStorage.setItem("read", newReadState.toString()); span2.textContent = newReadState ? `◼ ( ${floorStatus.readCounter} )` : `▶ ( ${floorStatus.readCounter} )`; if (newReadState) { autoRunStart(); } else { clearInterval(readInterval); } }; } function createButtonContainer() { let ul = document.querySelector(".sidebar-footer-wrapper"); let container = document.createElement("div"); container.style.display = "flex"; container.style.justifyContent = "center"; container.style.gap = "8px"; // 按鈕之間的間距 ul.prepend(container); return container; } function addAutoBtn() { let container = document.querySelector(".sidebar-footer-wrapper > div") || createButtonContainer(); let li = document.createElement("li"); li.className = "sidebar-section-link-wrapper"; li.style.margin = "0"; let a = document.createElement("a"); a.id = "startA2"; a.className = "ember-view sidebar-section-link sidebar-row"; //let a2 = document.createElement('a'); //a2.id = "directUrl"; //a2.className = "ember-view sidebar-section-link sidebar-row"; //a2.href = ""; let span1 = document.createElement("span"); span1.className = "sidebar-section-link-prefix icon"; let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.classList.add("fa", "d-icon", "d-icon-angle-down", "svg-icon", "prefix-icon", "svg-string"); let use = document.createElementNS("http://www.w3.org/2000/svg", "use"); use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#angle-down"); let span2 = document.createElement("span"); span2.className = "sidebar-section-link-content-text"; span2.id = "btnAutoRead2"; //span2.textContent = "▼"; svg.appendChild(use); span1.appendChild(svg); a.appendChild(span1); a.appendChild(span2); //li.appendChild(a2); li.appendChild(a); container.appendChild(li); a.onclick = function () { const currentlyReading = localStorage.getItem("read") === "true"; const newReadState = !currentlyReading; localStorage.setItem("read", newReadState.toString()); document.getElementById("btnAutoRead").textContent = newReadState ? `◼ ( ${floorStatus.readCounter} )` : `▶ ( ${floorStatus.readCounter} )`; if (newReadState) { autoRunStart(true); } else { clearInterval(readInterval); } }; } function addCleanBtn() { let container = document.querySelector(".sidebar-footer-wrapper > div") || createButtonContainer(); let li = document.createElement("li"); li.className = "sidebar-section-link-wrapper"; li.style.margin = "0"; let a = document.createElement("a"); a.id = "cleanA"; a.className = "ember-view sidebar-section-link sidebar-row"; let span1 = document.createElement("span"); span1.className = "sidebar-section-link-prefix icon"; let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.classList.add("fa", "d-icon", "d-icon-discourse-sparkles", "svg-icon", "prefix-icon", "svg-string"); let use = document.createElementNS("http://www.w3.org/2000/svg", "use"); use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#discourse-sparkles"); let span2 = document.createElement("span"); span2.className = "sidebar-section-link-content-text"; span2.id = "btnAutoReadReset"; //span2.textContent = "✨"; svg.appendChild(use); span1.appendChild(svg); a.appendChild(span1); a.appendChild(span2); li.appendChild(a); container.appendChild(li); li.onclick = function () { floorStatus.readCounter = 0; localStorage.setItem("readCounter", floorStatus.readCounter.toString()); document.getElementById("btnAutoRead").textContent = localStorage.getItem("read") === "true" ? `◼ ( ${floorStatus.readCounter} )` : `▶ ( ${floorStatus.readCounter} )`; }; } function addSettingBtn() { let container = document.querySelector(".sidebar-footer-wrapper > div") || createButtonContainer(); let li = document.createElement("li"); li.className = "sidebar-section-link-wrapper"; li.style.margin = "0"; let a = document.createElement("a"); a.id = "settingA"; a.className = "ember-view sidebar-section-link sidebar-row"; let span1 = document.createElement("span"); span1.className = "sidebar-section-link-prefix icon"; let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.classList.add("fa", "d-icon", "d-icon-ellipsis", "svg-icon", "prefix-icon", "svg-string"); let use = document.createElementNS("http://www.w3.org/2000/svg", "use"); use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#ellipsis"); let span2 = document.createElement("span"); span2.className = "sidebar-section-link-content-text"; span2.id = "btnAutoReadSetting"; //span2.textContent = "⚙️"; svg.appendChild(use); span1.appendChild(svg); a.appendChild(span1); a.appendChild(span2); li.appendChild(a); container.appendChild(li); a.onclick = function (e) { e.preventDefault(); const existingDialog = document.querySelector(".settings-dialog"); if (existingDialog) { existingDialog.remove(); } else { createSettingsDialog(); } }; } function createSettingsDialog() { const dialog = document.createElement("div"); dialog.className = "settings-dialog"; dialog.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--primary-low); padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); z-index: 1000; min-width: 300px; opacity: 0; transition: opacity 0.3s ease; `; // 讓dialog淡入顯示 setTimeout(() => (dialog.style.opacity = "1"), 10); const title = document.createElement("h3"); title.textContent = "設定"; title.style.marginBottom = "20px"; // 藍點模式設定行 const blueDotRow = createSettingRow("藍點模式", "blueDotMode"); const readLimitRow = createInputRow("讀多少休息呢?", "stopLimit", 350); const refreshRateForBlueDotMode = createInputRow("藍點模式速度", "refreshRateBlueDot", 300); const refreshRateForNormalMode = createInputRow("正常模式速度", "refreshRateNormal", 2400); const reloadWaitTime = createInputRow("藍點卡住多久要刷新呢?(s)", "reloadWaitTime", 25); const debugPanel = createSettingRow("開啟偵錯台", "debugPanel"); // 創建清除閱讀序列行 const clearReadSequenceRow = document.createElement("div"); clearReadSequenceRow.style.cssText = ` display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; `; const clearLabel = document.createElement("label"); const unreadList = JSON.parse(localStorage.getItem("unreadList") || "[]"); clearLabel.textContent = `閱讀序列: ${unreadList.length} 帖`; clearLabel.style.marginRight = "10px"; const clearButton = document.createElement("button"); clearButton.textContent = "清除"; clearButton.style.cssText = ` padding: 6px 12px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background 0.2s ease; `; clearButton.addEventListener("mouseover", () => { clearButton.style.background = "#c82333"; }); clearButton.addEventListener("mouseout", () => { clearButton.style.background = "#dc3545"; }); clearButton.addEventListener("click", () => { localStorage.setItem("unreadList", "[]"); addDebugTextarea("[INFO] 清除閱讀序列"); clearLabel.textContent = `閱讀序列: 0 帖`; alert("閱讀序列已清除,下次翻閱文章時,將重新取得列表。"); }); clearReadSequenceRow.appendChild(clearLabel); clearReadSequenceRow.appendChild(clearButton); // 創建關閉按鈕 const closeButton = document.createElement("button"); closeButton.textContent = "關閉"; closeButton.style.cssText = ` margin-top: 20px; padding: 8px 16px; background: var(--secondary); border: none; border-radius: 4px; cursor: pointer; transition: background 0.2s ease; width: 100%; `; closeButton.addEventListener("mouseover", () => { closeButton.style.background = "var(--header_background)"; }); closeButton.addEventListener("mouseout", () => { closeButton.style.background = "var(--secondary)"; }); closeButton.addEventListener("click", () => { dialog.style.opacity = "0"; setTimeout(() => dialog.remove(), 300); }); dialog.appendChild(title); dialog.appendChild(blueDotRow); dialog.appendChild(readLimitRow); dialog.appendChild(refreshRateForBlueDotMode); dialog.appendChild(refreshRateForNormalMode); dialog.appendChild(reloadWaitTime); dialog.appendChild(debugPanel); dialog.appendChild(clearReadSequenceRow); dialog.appendChild(closeButton); document.body.appendChild(dialog); return dialog; } function createSettingRow(labelText, storageKey) { const settingRow = document.createElement("div"); settingRow.style.cssText = ` display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; `; const label = document.createElement("label"); label.textContent = labelText; label.style.marginRight = "10px"; const toggleSwitch = document.createElement("label"); toggleSwitch.className = "switch"; toggleSwitch.style.cssText = ` position: relative; display: inline-block; width: 60px; height: 34px; `; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.style.cssText = ` opacity: 0; width: 0; height: 0; `; // 初始化時讀取localStorage的值 // 檢查並設定預設值 if (localStorage.getItem(storageKey) === null) { localStorage.setItem(storageKey, true); } const slider = document.createElement("span"); slider.className = "slider"; slider.style.cssText = ` position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; `; // 添加滑動圓球 const ball = document.createElement("span"); ball.style.cssText = ` position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: var(--primary-low); transition: .4s; border-radius: 50%; `; slider.appendChild(ball); const savedValue = localStorage.getItem(storageKey) || "false"; if (savedValue === "true") { checkbox.checked = true; slider.style.backgroundColor = "#2196F3"; ball.style.transform = "translateX(26px)"; } checkbox.addEventListener("change", function () { try { slider.style.backgroundColor = this.checked ? "#2196F3" : "#ccc"; ball.style.transform = this.checked ? "translateX(26px)" : "translateX(0)"; // 儲存到localStorage localStorage.setItem(storageKey, this.checked.toString()); } catch (error) { console.error("無法存取 localStorage:", error); } if (storageKey === "blueDotMode" && localStorage.getItem("read") === "true") { clearInterval(readInterval); autoRunStart(); } if (storageKey === "debugPanel") { if (!this.checked) { addDebugTextarea("[INFO] Debug panel closed"); document.getElementById("debugTextarea").remove(); } else { addDebugTextarea(); } } }); toggleSwitch.appendChild(checkbox); toggleSwitch.appendChild(slider); settingRow.appendChild(label); settingRow.appendChild(toggleSwitch); return settingRow; } function createInputRow(labelText, storageKey, defaultValue) { const inputRow = document.createElement("div"); inputRow.style.cssText = ` display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; `; const label = document.createElement("label"); label.textContent = labelText; label.style.marginRight = "10px"; const input = document.createElement("input"); input.type = "text"; input.style.cssText = ` width: 5em; margin-left: auto; padding: 5px; border: 1px solid #ccc; border-radius: 4px; `; const storedValue = localStorage.getItem(storageKey); if (storedValue === null) { input.value = defaultValue.toString(); } else { input.value = storedValue; } input.addEventListener("input", function () { this.value = this.value.replace(/[^0-9]/g, ""); try { // 儲存到localStorage localStorage.setItem(storageKey, this.value); } catch (error) { console.error("無法存取 localStorage:", error); } }); inputRow.appendChild(label); inputRow.appendChild(input); return inputRow; } function cleanTransition() { const style = document.createElement("style"); style.innerHTML = ` .read-state.read { transition: none !important; } `; document.head.appendChild(style); } function checkContinue() { if (localStorage.getItem("read") === "true") { localStorage.setItem("read", "false"); document.getElementById("btnAutoRead").click(); } } function setDefaultLocalStorage(key, defaultValue) { if (localStorage.getItem(key) === null) { localStorage.setItem(key, defaultValue); } } function checkAllLocalStorage() { setDefaultLocalStorage("isFirstRun", "false"); setDefaultLocalStorage("read", "false"); setDefaultLocalStorage("autoLikeEnabled", "false"); //默认关闭自动点赞 setDefaultLocalStorage("unreadList", "[]"); setDefaultLocalStorage("readUrl", "https://linux.do/t/topic/1"); setDefaultLocalStorage("blueDotMode", "true"); setDefaultLocalStorage("stopLimit", "250"); setDefaultLocalStorage("refreshRateBlueDot", "350"); setDefaultLocalStorage("refreshRateNormal", "2400"); setDefaultLocalStorage("reloadWaitTime", "25"); setDefaultLocalStorage("debugPanel", "false"); } // 检查是否是第一次运行脚本 function checkFirstRun() { if (localStorage.getItem("debugPanel") === "true") { addDebugTextarea(); } addDebugTextarea("[INFO] Init data"); checkAllLocalStorage(); // add element createButtonContainer(); addAutoBtn(); addCleanBtn(); addSettingBtn(); addStartBtn(); cleanTransition(); checkContinue(); } // 1. 创建一个函数来处理 article 元素 function handleArticle(articleElement) { // 2. 提取 article 的 id 數字 const postId = parseInt(articleElement.id.replace("post_", "")); // 3. 生成文字 const text = postId + " F"; // 4. 在 [[代碼插入處]] 插入文字 (修改部分) const avatarDiv = articleElement.querySelector(".topic-avatar"); if (avatarDiv === null) return; const existingText = avatarDiv.querySelector(`div[id='post-${postId}-f']`); // 檢查是否存在新添加的文字 if (!existingText) { // 如果不存在,才插入 const newText = document.createElement("div"); newText.textContent = text; newText.id = `post-${postId}-f`; // 添加 ID newText.style.textAlign = "center"; // 設置文字居中 newText.style.cursor = "pointer"; // 設置鼠標指針為鏈接樣式 avatarDiv.appendChild(newText); } } function addFloorListener() { // 6. 处理现有的 article 元素 const articleElements = Array.from(document.querySelectorAll("article")).filter((articleElement) => articleElement.id !== undefined); articleElements.forEach((articleElement) => { handleArticle(articleElement); }); // 7. 监听新的 article 元素的添加 const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { //addDebugTextarea(mutation); if (mutation.type === "childList" && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach((node) => { /*if (node.tagName === 'ARTICLE') { handleArticle(node); addDebugTextarea("run F"); }*/ // 6. 处理现有的 article 元素 const articleElements = Array.from(document.querySelectorAll("article")).filter((articleElement) => articleElement.id !== undefined); articleElements.forEach((articleElement) => { handleArticle(articleElement); }); }); } }); }); observer.observe(document.body, {childList: true, subtree: true}); } (function () { ("use strict"); checkFirstRun(); addFloorListener(); })();