您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
したらば掲示板を実況向けにするスクリプト
// ==UserScript== // @name koyomate // @namespace gunjobiyori.com // @version 0.1.0 // @description したらば掲示板を実況向けにするスクリプト // @author euro_s // @match https://jbbs.shitaraba.net/bbs/read.cgi/internet/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @run-at document-start // @license MIT // ==/UserScript== (function () { "use strict"; function add_replied_comment_loaded(replied_elem, has_anc_elem) { var dl = document.createElement("dl"); var cp_replied_dt = replied_elem.cloneNode(true); dl.classList.add("rep-comment"); dl.appendChild(cp_replied_dt); dl.style.display = "none"; has_anc_elem.insertBefore(dl, has_anc_elem.firstElementChild); } function add_replied_comment_xhr(replied_elem, has_anc_elem) { var dl = document.createElement("dl"); var cp_replied_dt = replied_elem.cloneNode(true); var cp_replied_dd = replied_elem.nextElementSibling.cloneNode(true); dl.classList.add("rep-comment"); dl.appendChild(cp_replied_dt); dl.appendChild(cp_replied_dd); dl.style.display = "none"; has_anc_elem.insertBefore(dl, has_anc_elem.firstElementChild); } function getBbsUrl() { const url = window.location.href; const splitted = url.split('/'); // URL ex: https://jbbs.shitaraba.net/bbs/read.cgi/internet/25835/1688993025/ // splitted: ["https:", "", "jbbs.shitaraba.net", "bbs", "read.cgi", "internet", "25835", "1688993025", ""] return `https://${splitted[2]}/${splitted[5]}/${splitted[6]}`; } // Function to update max-width of .img-popup function updateMaxWidth() { // Calculate max width as 80% of window's width let maxWidth = window.innerWidth * 0.8; // Get all .img-popup elements and update their max-width let popups = document.querySelectorAll('.img-popup'); popups.forEach(popup => { popup.style.maxWidth = maxWidth + 'px'; }); } function reStyle() { const thread_body = document.getElementById("thread-body"); var dts = Array.from(document.querySelectorAll("dl#thread-body > dt")); var dds = Array.from(document.querySelectorAll("dl#thread-body > dd")); var tables = Array.from(document.querySelectorAll("table")); // Clear the original elements dts.forEach((dt) => dt.remove()); dds.forEach((dd) => dd.remove()); tables.forEach((table) => table.remove()); // Combine the dt and dd contents and append them to the parent dts.forEach((dt, index) => { let outerDiv = document.createElement("div"); outerDiv.id = dt.id; outerDiv.classList.add("comment"); let meta = document.createElement("span"); meta.innerText = dt.querySelector("a").innerText + ": "; let comment = document.createElement("span"); let aTags = dds[index].querySelectorAll("a"); let imageLinks = Array.from(aTags).filter(a => a.innerText.match(/\.(jpeg|jpg|gif|png)$/i) !== null); imageLinks.forEach((link) => { let popup = document.createElement('img'); popup.src = link.innerText; popup.className = 'img-popup'; link.appendChild(popup); }); // Update the max-width of all .img-popup elements updateMaxWidth(); if (dt.querySelector("a").innerText == "1") { comment.innerHTML = dds[index].innerHTML; } else { comment.innerHTML = dds[index].innerHTML.replace(/<br>/g, " ").trim(); } outerDiv.appendChild(meta); outerDiv.appendChild(comment); thread_body.appendChild(outerDiv); }); const small = document.querySelector('body > small'); if (small) { const aTags = small.querySelectorAll('a'); aTags.forEach((a) => a.remove()); const bbsUrl = getBbsUrl(); const a = document.createElement('a'); a.href = bbsUrl; a.innerText = '掲示板に戻る'; small.appendChild(a); } } function add_replied_comment() { var has_anc = document.querySelectorAll("#thread-body > div > span > span.res"); var reg = /\/(\d+)$/; for (var i = 0; i < has_anc.length; i++) { var replied_url = has_anc[i].querySelector('a').getAttribute('href'); var reg_result = reg.exec(replied_url); var replied_id; if (reg_result) { replied_id = reg_result[1]; } else { continue; } var replied_elem = document.getElementById("comment_" + replied_id); if (replied_elem) { add_replied_comment_loaded(replied_elem, has_anc[i]); has_anc[i].addEventListener("mouseenter", function () { this.firstElementChild.style.display = ""; }); } else { has_anc[i].addEventListener("mouseenter", { replied_id: replied_id, replied_url: replied_url, has_anc_elem: has_anc[i], handleEvent: function () { if (this.has_anc_elem.firstElementChild.tagName === "DL") { this.has_anc_elem.firstElementChild.style.display = ""; } else { const xhr = new XMLHttpRequest(); xhr.responseType = "document"; xhr.open("get", this.replied_url, true); xhr.timeout = 5 * 1000; xhr.addEventListener("load", { replied_id: this.replied_id, has_anc_elem: this.has_anc_elem, handleEvent: function (res) { if (res.target.status !== 200) { return; } replied_elem = res.target.responseXML.getElementById("comment_" + this.replied_id); console.log(replied_elem); if (replied_elem) { add_replied_comment_xhr(replied_elem, this.has_anc_elem); } this.has_anc_elem.firstElementChild.style.display = ""; } }); xhr.send(); } } }); } has_anc[i].addEventListener("mouseleave", function () { if (this.firstElementChild.tagName === "DL") { this.firstElementChild.style.display = "none"; } }); } } function upDownButtons() { // Create a new button element for scrolling to bottom const buttonDown = document.createElement("button"); buttonDown.id = "scrollToBottomButton"; buttonDown.innerHTML = ` <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIgdmlld0JveD0iMCAwIDMwIDMwIj4KICAgIDxwYXRoIGQ9Ik0xNSAyMEw1IDEwaDIwbC0xMCAxMHoiIGZpbGw9ImJsYWNrIi8+Cjwvc3ZnPgo="/> `; // Create a new button element for scrolling to top const buttonUp = document.createElement("button"); buttonUp.id = "scrollToTopButton"; buttonUp.innerHTML = ` <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIgdmlld0JveD0iMCAwIDMwIDMwIj4KICAgIDxwYXRoIGQ9Ik0xNSAxMEw1IDIwaDIwbC0xMCAtMTB6IiBmaWxsPSJibGFjayIvPgo8L3N2Zz4K"/> `; // Add the buttons to the document body document.body.append(buttonDown, buttonUp); // Attach an event listener to the buttons to handle clicks buttonDown.addEventListener("click", function () { window.scrollTo({ top: document.body.scrollHeight, // Scroll to the bottom of the page behavior: "smooth", // Animate the scroll }); }); buttonUp.addEventListener("click", function () { window.scrollTo({ top: 0, // Scroll to the top of the page behavior: "smooth", // Animate the scroll }); }); } let isAutoReloading = true; function createProgressBar() { // Create the SVG element const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("width", "40"); svg.setAttribute("height", "40"); svg.style.position = "fixed"; svg.style.right = "20px"; svg.style.top = "120px"; svg.style.cursor = "pointer"; // Create the background circle const bgCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); bgCircle.setAttribute("cx", "20"); bgCircle.setAttribute("cy", "20"); bgCircle.setAttribute("r", "16"); bgCircle.setAttribute("stroke", "#ddd"); bgCircle.setAttribute("stroke-width", "4"); bgCircle.setAttribute("fill", "none"); svg.appendChild(bgCircle); // Create the foreground circle (progress bar) const fgCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); fgCircle.setAttribute("cx", "20"); fgCircle.setAttribute("cy", "20"); fgCircle.setAttribute("r", "16"); fgCircle.setAttribute("stroke", "#3498db"); fgCircle.setAttribute("stroke-width", "4"); fgCircle.setAttribute("fill", "none"); fgCircle.style.strokeDasharray = "113.04"; // 2 * PI * r (approx.) fgCircle.style.strokeDashoffset = "113.04"; fgCircle.style.transform = "rotate(-90deg)"; fgCircle.style.transformOrigin = "50% 50%"; svg.addEventListener("click", function () { isAutoReloading = !isAutoReloading; // toggle auto reloading // Change the color of the progress bar based on the auto reloading status fgCircle.setAttribute("stroke", isAutoReloading ? "#3498db" : "#e74c3c"); }); svg.appendChild(fgCircle); document.body.appendChild(svg); return fgCircle; } let progressBar; function updateProgressBar(timeElapsed, totalTime) { const progress = timeElapsed / totalTime; const strokeLength = 113.04 * progress; progressBar.style.strokeDashoffset = 113.04 - strokeLength; } async function autoReload() { progressBar = createProgressBar(); let elapsed = 0; async function run() { if (isAutoReloading) { elapsed += 50; // update every 50 msec updateProgressBar(elapsed, 5000); if (elapsed >= 5000) { await getMessage(); elapsed = 0; } setTimeout(run, 50); // set the next run } } run(); // initial run } let toBottom = true; let lastScrollTop = 0; const FETCH_TIMEOUT = 1000; async function getMessage() { const lastMsg = document.querySelector("dl#thread-body > div.comment:last-child"); const lastId = lastMsg.id.replace('comment_', ''); const url = location.href + lastId + '-n'; try { const response = await Promise.race([ fetch(url), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), FETCH_TIMEOUT) ) ]); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } else { const arrayBuffer = await response.arrayBuffer(); const html = new TextDecoder("euc-jp").decode(arrayBuffer); const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); var dts = Array.from(doc.querySelectorAll("dl#thread-body > dt")); var dds = Array.from(doc.querySelectorAll("dl#thread-body > dd")); for (let i = 0; i < dts.length; i++) { if (dts[i].id == lastMsg.id) { continue; } let outerDiv = document.createElement("div"); outerDiv.id = dts[i].id; outerDiv.classList.add("comment"); let meta = document.createElement("span"); meta.innerText = dts[i].querySelector("a").innerText + ": "; let aTags = dds[i].querySelectorAll("a"); let imageLinks = Array.from(aTags).filter(a => a.innerText.match(/\.(jpeg|jpg|gif|png)$/i) !== null); imageLinks.forEach((link) => { let popup = document.createElement('img'); popup.src = link.innerText; popup.className = 'img-popup'; link.appendChild(popup); }); let comment = document.createElement("span"); comment.innerHTML = dds[i].innerHTML.replace(/<br>/g, " ").trim(); outerDiv.appendChild(meta); outerDiv.appendChild(comment); lastMsg.parentNode.appendChild(outerDiv); } if (toBottom) { window.scrollTo(0, document.body.scrollHeight); } } } catch (e) { console.error('Fetch failed!', e); } } function bottomEvent() { window.addEventListener("scroll", function () { const st = window.scrollY; // Check if we're at the bottom of the page if (st < lastScrollTop) { toBottom = false; } else if ( window.innerHeight + window.scrollY >= document.body.offsetHeight ) { toBottom = true; } lastScrollTop = st <= 0 ? 0 : st; }); } // Ensure the operation is performed after the DOM is fully loaded window.addEventListener( "load", async function () { new MutationObserver(add_replied_comment).observe( document.querySelector('#thread-body'), { childList: true } ); reStyle(); add_replied_comment(); upDownButtons(); createProgressBar(); bottomEvent(); await autoReload(); // Scroll to the bottom of the page window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth", }); }, false ); // Update the max-width of all .img-popup elements whenever the window is resized window.addEventListener('resize', updateMaxWidth, false); // ex. https://jbbs.shitaraba.net/bbs/read.cgi/internet/25835/1688993025/413-n const match = window.location.href.match(/(.+internet\/\d+\/\d+\/).+$/); if (match) { const url = match[1]; window.location.href = url; } //////////////////////////////////////////////////////////////////////////////// // CSS //////////////////////////////////////////////////////////////////////////////// GM_addStyle(` #thread-body { margin-left: 30px !important; margin-right: 30px !important; line-height: 2rem !important; } .site-header { display: none !important; } #new_response { display: none !important; } #g_floating_tag_zone { display: none !important; } #scrollToBottomButton, #scrollToTopButton { position: fixed; right: 20px; z-index: 10000; padding: 5px; cursor: pointer; background: #ddd; border: none; border-radius: 5px; transition: background 0.2s; } #scrollToBottomButton { top: 70px; } #scrollToTopButton { top: 20px; } #scrollToBottomButton:hover, #scrollToTopButton:hover { background: #bbb; } .img-popup { display: none; position: absolute; z-index: 1; border: 1px solid #ddd; } a:hover .img-popup { display: block; } `); })();