// ==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 || '