// ==UserScript== // @name Holotower Archive Backlink // @namespace http://tampermonkey.net/ // @match https://boards.holotower.org/* // @match https://holotower.org/* // @version 1.0 // @description Converts >>12345 orphaned posts into backlinks to the Holotower archive. This is bad! // @grant GM_xmlhttpRequest // @connect archive.holotower.org // @icon https://boards.holotower.org/favicon.gif // ==/UserScript== (function() { 'use strict'; const quoteRegex = /(^|\s|[^\w<>])>>(\d+)\b/g; const cache = {}; const backgroundColor = "#4b3b5c"; const linkColor = "#ffa900"; function createTooltip(content, x, y) { const wrapper = document.createElement("div"); wrapper.style.position = "absolute"; wrapper.style.top = `${y + 10}px`; wrapper.style.left = `${x + 250}px`; wrapper.style.zIndex = "10000"; const tooltip = document.createElement("div"); tooltip.innerHTML = content; tooltip.style.backgroundColor = backgroundColor; tooltip.style.color = "#fff"; tooltip.style.padding = "10px"; tooltip.style.borderRadius = "8px"; tooltip.style.minWidth = "550px"; tooltip.style.maxWidth = "800px"; tooltip.style.whiteSpace = "normal"; tooltip.style.wordWrap = "break-word"; tooltip.style.overflow = "auto"; tooltip.style.boxShadow = "0 4px 12px rgba(0,0,0,0.5)"; tooltip.style.position = "relative"; // left horn const leftHorn = document.createElement("img"); leftHorn.src = "https://boards.holotower.org/static/horn_left.png"; leftHorn.style.position = "absolute"; leftHorn.style.top = "-190px"; leftHorn.style.left = "-200px"; // Add more space to avoid overlapping tooltip leftHorn.style.width = "200px"; leftHorn.style.height = "320px"; leftHorn.style.pointerEvents = "none"; // right horn const rightHorn = document.createElement("img"); rightHorn.src = "https://boards.holotower.org/static/horn_right.png"; rightHorn.style.position = "absolute"; rightHorn.style.top = "-190px"; rightHorn.style.right = "-200px"; rightHorn.style.width = "200px"; rightHorn.style.height = "320px"; rightHorn.style.pointerEvents = "none"; // Add everything to wrapper wrapper.appendChild(leftHorn); wrapper.appendChild(rightHorn); wrapper.appendChild(tooltip); document.body.appendChild(wrapper); return wrapper; } function replaceQuotesInTextNode(textNode) { const text = textNode.textContent; if (!quoteRegex.test(text)) return; const span = document.createElement("span"); span.innerHTML = text.replace(quoteRegex, (match, pre, id) => { return `${pre}>>${id}`; }); textNode.parentNode.replaceChild(span, textNode); } function scanAndReplaceQuotes() { const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); const nodesToReplace = []; let node; while ((node = walker.nextNode())) { if (!node.parentNode.closest("a")) { if (quoteRegex.test(node.textContent)) { nodesToReplace.push(node); } } } for (const n of nodesToReplace) { replaceQuotesInTextNode(n); } } function attachHoverHandlers() { document.body.addEventListener("mouseover", function(e) { if (e.target.matches(".hover-quote")) { const id = e.target.dataset.id; const rect = e.target.getBoundingClientRect(); const x = rect.left + window.scrollX; const y = rect.top + window.scrollY; if (document.querySelector(".quote-tooltip")) return; const showTooltip = (html) => { const temp = document.createElement("div"); temp.innerHTML = html.trimStart(); // side arrow removal from the archive temp.querySelectorAll('div.sideArrows').forEach(div => div.remove()); temp.querySelectorAll('div.post.reply').forEach(div => { div.style.backgroundColor = backgroundColor; }); // drop the post reply class to remove the boxshadow temp.querySelectorAll("div.post.reply").forEach(div => { div.classList.remove("post"); div.classList.remove("reply"); }); const baseUrl = "https://archive.holotower.org"; temp.querySelectorAll('a[href^="/"]').forEach(link => { link.href = baseUrl + link.getAttribute("href"); }); temp.querySelectorAll('img').forEach(img => { const src = img.getAttribute("src"); if (src && src.startsWith("/")) { img.setAttribute("src", baseUrl + src); } img.removeAttribute("onerror"); }); const tooltip = createTooltip(temp.innerHTML, x, y); e.target.addEventListener("mouseleave", () => { tooltip.remove(); }, { once: true }); e.target.addEventListener("click", (ev) => { ev.preventDefault(); const threadLink = temp.querySelector("a[href^='https://archive.holotower.org/thread/']"); if (threadLink) { window.location.href = threadLink.href; } }); }; if (cache[id]) { showTooltip(cache[id]); } else { GM_xmlhttpRequest({ method: "GET", url: `https://archive.holotower.org/hlgg/post/${id}`, onload: function(response) { try { const data = JSON.parse(response.responseText); cache[id] = data.html_content || '



This post was missing from the archive.





'; showTooltip(data.html_content); } catch (err) { console.error("Failed to parse JSON:", err); } } }); } } }); } scanAndReplaceQuotes(); attachHoverHandlers(); const observer = new MutationObserver(() => { scanAndReplaceQuotes(); }); observer.observe(document.body, { childList: true, subtree: true }); })();