您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Block people that are complete bananas on Gamebanana.
// ==UserScript== // @name BananaBlocker // @namespace http://tampermonkey.net/ // @version 1.2 // @description Block people that are complete bananas on Gamebanana. // @author PBalint817 // @icon https://images.gamebanana.com/static/img/banana.png // @match https://gamebanana.com/* // @grant GM_getValue // @grant GM_setValue // @license MIT // ==/UserScript== 'use strict'; class User { static filter = { Default: -1, Keep: 0, Hide: 1, Remove: 2 }; static defaults = { Posts: User.filter.Remove, Comments: User.filter.Keep, Replies: User.filter.Keep } constructor(id, o=undefined){ if (typeof(id) !== 'number'){ throw new Error('Expected number as id, got ' + typeof(id)) } this.id = id; if (!o || typeof(o) !== 'object'){ o = {} } for (const key in User.defaults){ const defaultValue = User.defaults[key] if (!(key in o)){ o[key] = User.filter.Default; continue; } let value = o[key]; if (value === undefined){ value = -1; } if ((typeof(value) !== typeof(defaultValue))){ console.warn(`The option '${key}' got a value of type '${typeof(value)}', expected '${typeof(defaultValue)}'`); o[key] = User.filter.Default; continue; } } for (const key in User.defaults){ this[key] = User.defaults[key] } for (const key in o){ if (!(key in this)){ if (key !== 'id'){ console.warn(`The option '${key}' is not valid and was ignored.`); } continue; } this[key] = o[key] } } } let disableOnProfile; let blockList; function loadConfig(){ let t = GM_getValue("disableOnProfile"); if (typeof(t) !== 'boolean'){ t = true; GM_setValue("disableOnProfile", true); } disableOnProfile = t; let userDefaults = GM_getValue("defaults"); if (typeof(userDefaults) !== 'object'){ userDefaults = {...User.defaults}; GM_setValue("defaults", userDefaults) } const validNumbers = new Set(Object.values(User.filter)); validNumbers.delete(User.filter.Default); for (const key in userDefaults){ if (!(key in User.defaults)){ console.warn(`The default for option '${key}' is not valid and was ignored.`); continue; } const defaultValue = User.defaults[key]; const value = userDefaults[key]; if (typeof(defaultValue) !== typeof(value)){ console.warn(`The default for option '${key}' got a value of type '${typeof(value)}', expected '${typeof(defaultValue)}'`); continue; } if (typeof(defaultValue) === 'number'){ if (!validNumbers.has(value)){ console.warn(`The default for option '${key}' was out of range.`) continue; } } User.defaults[key] = value; } t = GM_getValue("blockList"); if (typeof(t) !== 'object'){ t = {}; GM_setValue("blockList", t); } blockList = t; for (let key in {...blockList}){ let value = blockList[key]; if (typeof(key) !== 'number'){ delete blockList[key]; key = +key; blockList[key] = value; } if (!Number.isSafeInteger(key) || key < 1){ delete blockList[key]; continue; } if (typeof(value) !== 'object'){ value = {}; } delete value.id; blockList[key] = new User(key, value); } } function saveConfig(){ GM_setValue('disableOnProfile', disableOnProfile); GM_setValue('defaults', User.defaults); GM_setValue('blockList', blockList); } loadConfig(); function getDefaultBlock(){ return Object.fromEntries(Object.keys(User.defaults).map(key => [key, User.filter.Default])) } function getBlockIncludingDefault(id, key){ if (!(id in blockList)){ return null; } const user = blockList[id] if (!(key in user)){ throw new Error(`tried to access invalid key from block list (id=${id}, key=${key})`); } const value = user[key]; if (value === User.filter.Default || value === undefined){ return -1; } return value; } function getBlockType(id, key){ if (!(id in blockList)){ return null; } const user = blockList[id] if (!(key in user)){ throw new Error(`tried to access invalid key from block list (id=${id}, key=${key})`); } const value = user[key]; if (value === User.filter.Default || value === undefined){ return User.defaults[key]; } return value; } const defaultTimeout = 5000; const blockerInjectedClassName = "BananaBlockerInjectedClass"; const redactedClassName = "BananaBlockerRedacted"; const blockedMsgHtml = `<i style="color: gray; cursor: pointer" class>Click to show blocked message.</i>`; const blockedPostHtml = `<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: darkred; z-index: 10; text-align: center; font-weight: bold; background-color: rgba(255, 255, 255, 0.5); text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); padding: 4px 8px; border-radius: 4px; pointer-events: none">Blocked</div>` function waitForElementOrLoad(selector, timeout, callback, failCallback) { const element = document.querySelector(selector); if (element) return callback(element); let isObserving = false; const observer = new MutationObserver(() => { const el = document.querySelector(selector); if (el) { observer.disconnect(); isObserving = false; if (callback){ callback(el); } } }); if (timeout){ window.addEventListener('load', () => { setTimeout(() => { if (!isObserving) return; observer.disconnect(); isObserving = false; if (failCallback){ failCallback(); } }, timeout); }); } isObserving = true; observer.observe(document.body, { childList: true, subtree: true }); } function filterAllSubmissions(rootElement) { const posts = [...rootElement.querySelectorAll(".PreviewWrapper")]; for (const post of posts){ const avatar = post.querySelector(".Avatar"); let isProfile = false; let href; if (avatar) { href = avatar.href; } else{ const navigatorLink = document.querySelector("#SubNavigator a"); if (!navigatorLink) continue; href = navigatorLink.href; isProfile = true; } if (typeof(href) !== 'string') continue; const split = href.split('/'); const userId = +split[split.length-1]; if (!userId) continue; let value = getBlockType(userId, 'Posts') if (!value){ continue; } if (isProfile && disableOnProfile){ if (value > User.filter.Hide){ value = User.filter.Hide; } } removeSubmission(post, value); } } function removeSubmission(post, mode){ const parent = post.parentElement; if (parent.classList.contains(blockerInjectedClassName)) { return; } parent.classList.add(blockerInjectedClassName); if (mode === 1){ parent.dataset._sinitialvisibility = "warn"; post.innerHTML += blockedPostHtml; } else{ parent.dataset._sinitialvisibility = "hide"; } } function main1(element){ const contentWrapper = element; filterAllSubmissions(document); const submissions = document.querySelector('#SubmissionsListModule'); const observer = new MutationObserver(() => { filterAllSubmissions(submissions); }); observer.observe(submissions, { childList: true, subtree: true }); } function filterAllComments(rootElement){ const comments = [...rootElement.querySelectorAll("div.Post")]; for (const comment of comments){ const memberlink = comment.querySelector(".MemberLink"); if (!memberlink) continue; const href = memberlink.href; if (typeof(href) !== 'string') continue; const split = href.split('/'); const userId = +split[split.length-1]; if (!userId) continue; const parentElement = comment.parentElement; if (parentElement.classList.contains("Replies")){ const value = getBlockType(userId, "Replies"); if (!value) continue; removeComment(comment, value) } else{ const value = getBlockType(userId, "Comments"); if (!value) continue; removeComment(comment, value) } } } function removeComment(comment, mode){ if (mode === 1){ const contents = comment.querySelector(".RichText"); if (!contents.classList.contains(blockerInjectedClassName)){ contents.classList.add(blockerInjectedClassName); const originalHTML = contents.innerHTML; const refresh = () => { if (contents.classList.contains(redactedClassName)){ contents.innerHTML = blockedMsgHtml; } else{ contents.innerHTML = originalHTML; } } const toggle = () => { if (contents.classList.contains(redactedClassName)) { contents.classList.remove(redactedClassName) } else { contents.classList.add(redactedClassName); } refresh(); }; contents.addEventListener('click', toggle); toggle(); const observer = new MutationObserver(() => { observer.disconnect(); refresh(); observer.observe(contents, {characterData: false, childList: true, attributes: false}) }) observer.observe(contents, {characterData: false, childList: true, attributes: false}); } } else { comment.remove(); } } function main2(element){ const commentWrapper = element; filterAllComments(document); const comments = document.querySelector('#PostsListModule'); const observer = new MutationObserver(() => { filterAllComments(comments); }); observer.observe(comments, { childList: true, subtree: true }); } function hideProfileSubmissions(rootElement){ const posts = [...rootElement.querySelectorAll(".PreviewWrapper")]; for (const post of posts) { } } function main3(targetSelector, element){ if (targetSelector !== "#IdentityModule"){ filterAllSubmissions(document); const submissions = document.querySelector(targetSelector); const observer = new MutationObserver(() => { filterAllSubmissions(submissions); }); observer.observe(submissions, { childList: true, subtree: true }); } const re = /^https:\/\/gamebanana\.com\/members\/\d+/; if (document.URL.match(re)){ waitForElementOrLoad("#IdentityModule .Content", defaultTimeout, injectUserBlockSetting, () => {}) } } function injectUserBlockSetting(element){ if (element.querySelector("#UserBlockerCheckboxInjected")) return; element.innerHTML += "<input type='checkbox' id='UserBlockerCheckboxInjected'>Block</input>" element.innerHTML += "<ul class='KeyInfo'></ul>" const ul = element.lastChild; const regexResult = [.../^https:\/\/gamebanana\.com\/members\/(\d+)/.exec(document.URL)] const targetId = regexResult[regexResult.length-1]; let optionHtml = "" for (const key in User.filter){ optionHtml += `<option value='${User.filter[key]}'>${key}</option>` } const baseId = "UserBlockerOption" for (const key in User.defaults){ const currentId = `${baseId}_${key}` ul.innerHTML += `<li><select id="${currentId}">${optionHtml}</select><small>${key}</small></li>`; ul.lastChild.firstChild.dataset.targetKey = key; } const btn = element.querySelector("input"); let listenerEnabled = false; function loopListener(e) { if (!listenerEnabled) return; const selectElement = e.target; const value = +selectElement.value; const targetKey = selectElement.dataset.targetKey; if (!Object.values(User.filter).includes(value)) return; if (!Object.keys(User.defaults).includes(targetKey)) return; const user = blockList[targetId]; user[targetKey] = value; updateBlock(user); refreshValues(); btn.checked = targetId in blockList; btn.dispatchEvent(new Event("change"), {bubbles: true}) } for (const li of ul.children){ const select = li.querySelector("select"); select.addEventListener('change', loopListener) } function refreshValues(){ if (!btn.checked) return; listenerEnabled = false; for (const key in User.defaults){ const currentId = `${baseId}_${key}` const select = document.querySelector('#'+currentId); const blockType = getBlockIncludingDefault(targetId, key); select.value = blockType select.dispatchEvent(new Event("change"), {bubbles: true}) } listenerEnabled = true; } btn.addEventListener('change', e => { if (e.target.checked){ ul.style.visibility = ""; if (!(targetId in blockList)){ if (listenerEnabled){ addBlock(targetId); } refreshValues(); } } else{ ul.style.visibility = "hidden"; if (listenerEnabled){ removeBlock(targetId); } } }) btn.checked = targetId in blockList; btn.dispatchEvent(new Event("change"), {bubbles: true}) refreshValues(); listenerEnabled = true; } function addBlock(targetId){ loadConfig(); blockList[targetId] = getDefaultBlock(); saveConfig(); } function updateBlock(user){ loadConfig(); if (!(user.id in blockList)){ return false; } blockList[user.id] = user; saveConfig(); return true; } function removeBlock(targetId){ loadConfig(); delete blockList[targetId]; saveConfig(); } // AI-generated because I got lazy (UI and JS in general is not my passion...) function showBlockListSetting() { // Create modal overlay const modalOverlay = document.createElement("div"); modalOverlay.style.position = "fixed"; modalOverlay.style.top = "0"; modalOverlay.style.left = "0"; modalOverlay.style.width = "100%"; modalOverlay.style.height = "100%"; modalOverlay.style.backgroundColor = "rgba(0, 0, 0, 0.7)"; modalOverlay.style.zIndex = "9999"; modalOverlay.style.display = "flex"; modalOverlay.style.justifyContent = "center"; modalOverlay.style.alignItems = "center"; // Create modal content const modalContent = document.createElement("div"); modalContent.style.backgroundColor = "#2b2b2b"; modalContent.style.padding = "20px"; modalContent.style.borderRadius = "8px"; modalContent.style.width = "80%"; modalContent.style.maxWidth = "600px"; modalContent.style.maxHeight = "80vh"; modalContent.style.overflow = "auto"; // Add title and close button modalContent.innerHTML = ` <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> <h2 style="margin: 0;">Global settings</h2> <button id="closeBlocklistModal" style="background: none; border: none; color: white; font-size: 20px; cursor: pointer;">×</button> </div> <div> <ul style="list-style-type: none"> <li> <small>Disable on profiles: </small> <input type='checkbox' id="BlockerConfig_ProfileToggle"/> </li> <li> <p>Defaults:</p> <ul class="KeyInfo" id="BlockerConfig_Defaults" style="list-style-type: none; margin-left: 20px"> </ul> </li> </ul> </div> `; let listenerEnabled = false; // Append content to overlay modalOverlay.appendChild(modalContent); // Add to document document.body.appendChild(modalOverlay); const defaults = document.querySelector("#BlockerConfig_Defaults"); let optionHtml = "" for (const key in User.filter){ if (User.filter[key] === User.filter.Default) continue; optionHtml += `<option value='${User.filter[key]}'>${key}</option>` } const baseId = "DefaultBlockerOption" for (const key in User.defaults){ const currentId = `${baseId}_${key}` defaults.innerHTML += `<li><small>${key}</small><select id="${currentId}">${optionHtml}</select></li>`; defaults.lastChild.querySelector("select").dataset.targetKey = key; } function loopListener(e) { if (!listenerEnabled) return; const selectElement = e.target; const value = +selectElement.value; const targetKey = selectElement.dataset.targetKey; if (!Object.values(User.filter).includes(value)) return; if (!Object.keys(User.defaults).includes(targetKey)) return; loadConfig(); User.defaults[targetKey] = value; saveConfig(); refreshValues(); } for (const li of defaults.children){ const select = li.querySelector("select"); select.addEventListener('change', loopListener) } const profileToggle = document.querySelector("#BlockerConfig_ProfileToggle") function refreshValues(){ listenerEnabled = false; profileToggle.checked = disableOnProfile; profileToggle.dispatchEvent(new Event("change"), {bubbles: true}); for (const key in User.defaults){ const currentId = `${baseId}_${key}` const select = document.querySelector('#'+currentId); const blockType = User.defaults[key]; select.value = blockType select.dispatchEvent(new Event("change"), {bubbles: true}) } listenerEnabled = true; } profileToggle.addEventListener("change", e => { if (!listenerEnabled) return; loadConfig(); disableOnProfile = e.target.checked; saveConfig(); refreshValues(); }) // Close button functionality const closeButton = modalOverlay.querySelector("#closeBlocklistModal"); closeButton.addEventListener("click", () => { document.body.removeChild(modalOverlay); }); // Close when clicking outside content modalOverlay.addEventListener("click", (e) => { if (e.target === modalOverlay) { document.body.removeChild(modalOverlay); } }); refreshValues(); listenerEnabled = true; } function injectBlockListSetting(mainLinks) { const blockerConfigId = "InjectedBlockerConfig"; const blockerConfig = mainLinks.querySelector(`#${blockerConfigId}`); if (blockerConfig){ return; // already injected } const btn = document.createElement("button") btn.id = blockerConfigId; btn.innerHTML = `<spriteicon class="SmallSettingsIcon GagsIcon"></spriteicon><div><div>Blocklist</div><small>View blocked users</small></div>` mainLinks.prepend(btn); btn.addEventListener("click", showBlockListSetting); } function main4(element){ const observer = new MutationObserver(() => { const mainLinks = element.querySelector("#SiteMenu .MainLinks"); if (!mainLinks) return; injectBlockListSetting(mainLinks); }); observer.observe(element, { childList: true, subtree: true, }); } waitForElementOrLoad('#SubmissionsListModule', defaultTimeout, main1, () => {}); waitForElementOrLoad('#PostsListModule', defaultTimeout, main2, () => {}); for (const selector of ["#SubmissionsList", "#ModSubmissionsModule", "#IdentityModule"]){ waitForElementOrLoad(selector, defaultTimeout, elem => {main3(selector, elem)}, () => {}); } waitForElementOrLoad('#PrimaryNav', defaultTimeout, main4, () => {});
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址