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