// ==UserScript== // @name bagscript // @description bag anti bot script // @version 0.5.1 // @license MIT // @namespace 9e7f6239-592e-409b-913f-06e11cc5e545 // @include https://8chan.moe/v/res/* // @include https://8chan.se/v/res/* // @include https://8chan.moe/barchive/res/* // @include https://8chan.se/barchive/res/* // @grant unsafeWindow // @run-at document-idle // @downloadURL none // ==/UserScript== // Script settings const SPOILER_BORDER = "3px solid red"; const RUDE_FORMATS = ["JPEG", "JPG", "PNG"]; const THREAD_NAME_FILTER = "/bag/"; // Debug settings const DISABLE_YOU_BYPASS = false; // State let manualBypass; let defaultSpoilerSrc; const settings = {}; let toolbarVisible = false; // Loader (new MutationObserver((_, observer) => { const threadTitle = document.querySelector("div.opHead > span.labelSubject"); if (threadTitle) { observer.disconnect(); if (!threadTitle.innerText.includes(THREAD_NAME_FILTER)) { return; } loadSettings(); loadToolbar(); const initialPosts = document.querySelectorAll(".postCell"); initialPosts.forEach((post) => { handleSpoilers(post); }); processAllPosts(); postObserver.observe(document, {childList: true, subtree: true}); } })).observe(document, {childList: true, subtree: true}); // New post observer const postObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === 1) { const isPost = node.classList.contains("postCell"); const isHoverPost = node.classList.contains("quoteTooltip"); const isInlineQuote = node.classList.contains("inlineQuote"); if (isPost) { handleSpoilers(node); const id = postId(node); unsafeWindow.posting.idsRelation[id].forEach((innerPost) => { processAllPostsById(id); }); node.querySelectorAll(".quoteLink").forEach((quoteLink) => { const quotedId = quoteLink.innerText.substring(2); const quotedPost = document.getElementById(quotedId); processSinglePost(quotedPost); }); } else if (isHoverPost || isInlineQuote) { handleSpoilers(node); processSinglePost(node); } } } } }); const processSinglePost = function(post) { const id = postId(post); const isNice = isNiceId(id) || isNicePost(post); if (isNice) { unblurPost(post); } else { blurPost(post); } } const processAllPosts = function() { for (const id in unsafeWindow.posting.idsRelation) { processAllPostsById(id); } document.querySelectorAll(".inlineQuote").forEach((inlineQuote) => { processSinglePost(inlineQuote); }); const hoverPost = document.querySelector(".quoteTooltip"); if (hoverPost) { processSinglePost(hoverPost); } } const processAllPostsById = function(id) { const innerPostsById = unsafeWindow.posting.idsRelation[id]; let isNice = isNiceId(id); for (const innerPost of innerPostsById) { const post = innerPost.parentElement; if (!isNice) { isNice = isNicePost(post); if (isNice) break; } } innerPostsById.forEach(innerPost => handlePost(innerPost.parentElement, isNice)); } const isNiceId = function(id) { if (!settings.enabled) return true; if (manualBypass[id]) return true; const innerPostsById = unsafeWindow.posting.idsRelation[id]; const isOp = innerPostsById.some(innerPost => innerPost.parentElement.classList.contains("opCell")); if (isOp) return true; const idAboveThreshold = innerPostsById.length >= settings.postThreshold; if (idAboveThreshold) return true; return false; } const isNicePost = function(post) { const postIsByYou = DISABLE_YOU_BYPASS ? false : post.querySelector(".youName"); if (postIsByYou) return true; const aboveBlThreshold = post.querySelectorAll(".postInfo > .panelBacklinks > a")?.length >= settings.backlinkThreshold; if (aboveBlThreshold) return true; if (settings.experimental) { const images = post.querySelectorAll("img"); const noImages = images.length === 0; if (noImages) return true; const hasFunImage = Array.from(images).some((image) => { const spoilerImage = image.getAttribute("data-spoiler") === "true" if (spoilerImage) return true; const format = image?.parentElement?.href?.split("/")?.[4]?.split(".")?.[1]?.toUpperCase(); if (format) { const notRudeImage = !RUDE_FORMATS.includes(format); if (notRudeImage) return true; } return false; }); if (hasFunImage) return true; const hasFunText = post.querySelector(".doomText, .moeText, .redText, .pinkText, .diceRoll"); if (hasFunText) return true; } return false; } const isRudeId = function(id) { return settings.experimental && unsafeWindow.posting.idsRelation[id].length === 3; } const handlePost = function(post, isNice) { let bypassButton = post.querySelector(".bypassButton"); if (isNice) { unblurPost(post); if (bypassButton) { bypassButton.style.display = "none"; } } else { blurPost(post); if (bypassButton) { bypassButton.style.display = "inline"; if (isRudeId(postId(post))) { bypassButton.style.border = "1px solid red"; } } else { bypassButton = bypassButtonForId(postId(post)); post.querySelector(".postInfo.title").appendChild(bypassButton); } } } const handleSpoilers = function(post) { const spoilers = post.querySelectorAll("img[src*='spoiler'], img[data-spoiler]"); if (!defaultSpoilerSrc) { defaultSpoilerSrc = spoilers[0]?.src; } spoilers.forEach(spoiler => { spoiler.setAttribute("data-spoiler", true); if (settings.revealSpoilers) { const fileName = spoiler.parentElement.href.split("/")[4].split(".")[0]; spoiler.src = `/.media/t_${fileName}`; spoiler.style.border = SPOILER_BORDER; } else { spoiler.src = defaultSpoilerSrc; spoiler.style.border = "0"; } }); } const blurPost = function(post) { post.style.display = settings.hideFiltered ? "none" : "block"; post.querySelectorAll("img").forEach((img) => { img.style.filter = `blur(${settings.blurStrength}px)`; }); } const unblurPost = function(post) { post.style.display = "block"; post.querySelectorAll("img").forEach((img) => { img.style.filter = ""; }); } const loadToolbar = function() { // Toolbar container const toolbar = document.createElement("div"); document.querySelector("body").appendChild(toolbar); toolbar.style.backgroundColor = "var(--navbar-text-color)"; toolbar.style.bottom = "0px"; toolbar.style.color = "var(--navbar-text-color)"; toolbar.style.display = "flex"; toolbar.style.gap = "1px"; toolbar.style.right = "0px"; toolbar.style.padding = "1px"; toolbar.style.position = "fixed"; // Toolbar contents container const toolbarContents = document.createElement("div"); toolbar.appendChild(toolbarContents); toolbarContents.style.display = "none"; toolbarContents.style.flexDirection = "column"; toolbarContents.style.gap = "1px"; toolbarContents.style.padding = "1px 1px 0 1px"; // Enable checkbox const enableContainer = container(); toolbarContents.appendChild(enableContainer); const enableLabel = label("Enable Filter"); enableContainer.appendChild(enableLabel); const enableCheckbox = checkbox(settings.enabled); enableContainer.appendChild(enableCheckbox); enableCheckbox.onchange = () => { settings.enabled = !settings.enabled; unsafeWindow.localStorage.setItem("bag_enabled", settings.enabled); if (settings.enabled) { processAllPosts(); postObserver.observe(document, {childList: true, subtree: true}); } else { postObserver.disconnect(); processAllPosts(); } } // Post threshold input const thresholdContainer = container(); toolbarContents.appendChild(thresholdContainer); const thresholdLabel = label("Post Threshold"); thresholdContainer.appendChild(thresholdLabel); const thresholdInput = input(settings.postThreshold); thresholdContainer.appendChild(thresholdInput); thresholdInput.onchange = () => { settings.postThreshold = thresholdInput.value; unsafeWindow.localStorage.setItem("bag_postThreshold", settings.postThreshold); processAllPosts(); } // Backlink threshold input const blThresholdContainer = container(); toolbarContents.appendChild(blThresholdContainer); const blThresholdLabel = label("Backlink Threshold"); blThresholdContainer.appendChild(blThresholdLabel); const blThresholdInput = input(settings.backlinkThreshold); blThresholdContainer.appendChild(blThresholdInput); blThresholdInput.onchange = () => { settings.backlinkThreshold = blThresholdInput.value; setSetting("bag_backlinkThreshold", settings.backlinkThreshold); processAllPosts(); } // Blur input const blurContainer = container(); toolbarContents.appendChild(blurContainer); const blurLabel = label("Blur Strength"); blurContainer.appendChild(blurLabel); const blurInput = input(settings.blurStrength); blurContainer.appendChild(blurInput); blurInput.onchange = () => { settings.blurStrength = blurInput.value; unsafeWindow.localStorage.setItem("bag_blurStrength", settings.blurStrength); processAllPosts(); } // Experimental checkbox const experimentalContaner = container(); toolbarContents.appendChild(experimentalContaner); const experimentalLabel = label("Experimental Heuristics"); experimentalContaner.appendChild(experimentalLabel); const experimentalCheckbox = checkbox(settings.experimental); experimentalContaner.appendChild(experimentalCheckbox); experimentalCheckbox.onchange = () => { settings.experimental = !settings.experimental; unsafeWindow.localStorage.setItem("bag_experimental", settings.experimental); if (!settings.experimental) { document.querySelectorAll('.innerPost').forEach(innerPost => { innerPost.style.borderRight = "1px solid var(--horizon-sep-color)"; }); document.querySelectorAll(".bypassButton").forEach(bypassButton => { bypassButton.style.border = "1px solid var(--horizon-sep-color)"; }); } processAllPosts(); } // Hide filtered checkbox const hideContainer = container(); toolbarContents.appendChild(hideContainer); const hideLabel = label("Hide Filtered"); hideContainer.appendChild(hideLabel); const hideCheckbox = checkbox(settings.hideFiltered); hideContainer.appendChild(hideCheckbox); hideCheckbox.onchange = () => { settings.hideFiltered = !settings.hideFiltered; unsafeWindow.localStorage.setItem("bag_hideFiltered", settings.hideFiltered); processAllPosts(); } // Reveal spoilers checkbox const revealContainer = container(); toolbarContents.appendChild(revealContainer); const revealLabel = label("Reveal Spoilers"); revealContainer.appendChild(revealLabel); const revealCheckbox = checkbox(settings.revealSpoilers); revealContainer.appendChild(revealCheckbox); revealCheckbox.onchange = () => { settings.revealSpoilers = !settings.revealSpoilers; unsafeWindow.localStorage.setItem("bag_revealSpoilers", settings.revealSpoilers); document.querySelectorAll(".postCell").forEach(post => handleSpoilers(post)); } // Toolbar toggle button const toggleButton = button(); toolbar.appendChild(toggleButton); toggleButton.innerText = "<<" toggleButton.style.backgroundColor = "var(--background-color)" toggleButton.onclick = () => { toolbarVisible = !toolbarVisible; toolbarContents.style.display = toolbarVisible ? "flex" : "none"; toggleButton.innerText = toolbarVisible ? ">>" : "<<"; } } // Post helpers const postId = function(post) { return post.querySelector('.labelId').innerText; } // LocalStorage Helpers const loadSettings = function() { manualBypass = getManualBypass(); settings.backlinkThreshold = getIntSetting("bag_backlinkThreshold", 3); settings.blurStrength = getIntSetting("bag_blurStrength", 10); settings.enabled = getBoolSetting("bag_enabled", true); settings.experimental = getBoolSetting("bag_experimental", true); settings.hideFiltered = getBoolSetting("bag_hideFiltered", false); settings.postThreshold = getIntSetting("bag_postThreshold", 4); settings.revealSpoilers = getBoolSetting("bag_revealSpoilers", false); } function setSetting(name, value) { unsafeWindow.localStorage.setItem(name, value); } function getSetting(name) { return unsafeWindow.localStorage.getItem(name); } function getBoolSetting(name, defaultValue) { const value = getSetting(name, defaultValue); if (value === null) return defaultValue; return value == "true"; } function getIntSetting(name, defaultValue) { const value = getSetting(name, defaultValue); if (value === null) return defaultValue; return parseInt(value); } function getManualBypass() { const threadId = unsafeWindow.location.pathname.match(/\/v\/res\/(\d+)/)[1]; const bypassVar = "bag_bypass_" + threadId; const bp = getSetting(bypassVar); return (bp === null) ? {} : JSON.parse(bp); } function setManualBypass() { const threadId = unsafeWindow.location.pathname.match(/\/v\/res\/(\d+)/)[1]; const bypassVar = "bag_bypass_" + threadId; const bypassData = JSON.stringify(manualBypass); unsafeWindow.localStorage.setItem(bypassVar, bypassData); } // HTML Helpers function container() { const container = document.createElement("div"); container.style.alignItems = "center"; container.style.backgroundColor = "var(--background-color)"; container.style.display = "flex"; container.style.gap = "0.25rem"; container.style.justifyContent = "space-between"; container.style.padding = "0.25rem"; return container; } function label(text) { const label = document.createElement("div"); label.innerText = text; label.style.color = "white"; return label; } function checkbox(initialValue) { const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.style.cursor = "pointer"; checkbox.checked = initialValue; return checkbox; } function input(initialValue) { const input = document.createElement("input"); input.size = 4; input.value = initialValue; return input; } function button() { const button = document.createElement("div"); button.style.alignItems = "center"; button.style.color = "var(--link-color)"; button.style.cursor = "pointer"; button.style.display = "flex"; button.style.padding = "0.25rem 0.75rem"; button.style.userSelect = "none"; return button; } function bypassButtonForId(id) { const border = isRudeId(id) ? "1px solid red" : "1px solid var(--horizon-sep-color)"; const bypassButton = button(); bypassButton.className = "bypassButton"; bypassButton.innerText = "+"; bypassButton.style.display = "inline"; bypassButton.style.marginLeft = "auto"; bypassButton.style.border = border; bypassButton.onclick = () => { bypassButton.style.display = "none"; manualBypass[id] = true; setManualBypass(); unsafeWindow.posting.idsRelation[id].forEach(otherPostInner => { unblurPost(otherPostInner.parentElement); }); }; return bypassButton; }