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