您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Foldable panel allowing mods with NSFW or Reshade tags to be excluded
// ==UserScript== // @name NexusMods Exclude Tags Manager V3 // @namespace http://tampermonkey.net/ // @version 3.1 // @description Foldable panel allowing mods with NSFW or Reshade tags to be excluded // @author ChatGPT // @match https://www.nexusmods.com/* // @grant none // @license MIT // @run-at document-end // ==/UserScript== (function() { // Do not run on individual mod pages if (/\/mods\/\d+($|[?#])/.test(window.location.pathname)) { return; // Stop script here } 'use strict'; const STORAGE_KEY = 'nexus_exclude_tags_memory'; const excludeTags = [ "Extreme violence", "Sexualised", "Swearing/Profanity", "Pornographic", "Suicide", "Self-harm", "Depression", "Body stigma", "Eating disorder", "Harmful substances", "ReShade" ]; // Loads stored tags function loadMemory() { try { const val = localStorage.getItem(STORAGE_KEY); if (val) return JSON.parse(val); } catch {} return []; } // Checks if all given tags are in the URL function urlContainsAllTags(url, tags) { const params = url.searchParams; const urlTags = new Set(params.getAll('excludedTag')); return tags.every(t => urlTags.has(t)); } // Applies the tags stored in the URL, reloads if necessary function applyStoredTagsInUrl() { const storedTags = loadMemory(); if (storedTags.length === 0) return false; const url = new URL(window.location.href); if (!urlContainsAllTags(url, storedTags)) { // Remove all excludedTags before adding the correct ones url.searchParams.delete('excludedTag'); storedTags.forEach(t => url.searchParams.append('excludedTag', t)); // Reload without adding to history, avoids infinite loop window.location.replace(url.toString()); return true; // reload done } return false; // no need for reloading } // ============ UI =============== function createUI() { const container = document.createElement('div'); container.style.position = 'fixed'; container.style.top = '61px'; container.style.right = '160px'; container.style.zIndex = '999999'; container.style.fontFamily = 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif'; container.style.userSelect = 'none'; container.style.color = '#eee'; container.style.width = '260px'; container.style.boxShadow = '0 0 12px rgba(0,0,0,0.9)'; container.style.backgroundColor = '#1b1f2a'; container.style.borderRadius = '8px'; // Foldable button const toggleBtn = document.createElement('button'); toggleBtn.textContent = 'Exclude Tags ▼'; toggleBtn.style.width = '100%'; toggleBtn.style.padding = '10px'; toggleBtn.style.border = 'none'; toggleBtn.style.borderRadius = '8px 8px 0 0'; toggleBtn.style.backgroundColor = '#2e3a4e'; toggleBtn.style.color = '#eee'; toggleBtn.style.fontWeight = 'bold'; toggleBtn.style.cursor = 'pointer'; toggleBtn.style.textAlign = 'center'; toggleBtn.style.userSelect = 'none'; toggleBtn.style.boxShadow = 'inset 0 -3px 6px rgba(0,0,0,0.3)'; container.appendChild(toggleBtn); // Panel content (buttons list + apply) const panel = document.createElement('div'); panel.style.padding = '10px'; panel.style.display = 'none'; // visible at the outset panel.style.maxHeight = '320px'; panel.style.overflowY = 'auto'; container.appendChild(panel); // Toggle panel let collapsed = true; toggleBtn.onclick = () => { collapsed = !collapsed; panel.style.display = collapsed ? 'none' : 'block'; toggleBtn.textContent = collapsed ? 'Exclude Tags ▲' : 'Exclude Tags ▼'; }; // Creating tag buttons function createTagBtn(tag) { const btn = document.createElement('button'); btn.textContent = tag; btn.style.margin = '4px 6px 4px 0'; btn.style.padding = '6px 14px'; btn.style.borderRadius = '6px'; btn.style.border = 'none'; btn.style.backgroundColor = '#2e3a4e'; btn.style.color = '#eee'; btn.style.cursor = 'pointer'; btn.style.whiteSpace = 'nowrap'; btn.style.fontSize = '14px'; btn.dataset.tag = tag; btn.dataset.selected = 'false'; btn.onclick = () => { if (btn.dataset.selected === 'true') { btn.dataset.selected = 'false'; btn.style.backgroundColor = '#2e3a4e'; } else { btn.dataset.selected = 'true'; btn.style.backgroundColor = '#d9534f'; } }; return btn; } // Container tags buttons const btnContainer = document.createElement('div'); panel.appendChild(btnContainer); excludeTags.forEach(tag => { btnContainer.appendChild(createTagBtn(tag)); }); // button apply const applyBtn = document.createElement('button'); applyBtn.textContent = 'Apply'; applyBtn.style.width = '100%'; applyBtn.style.marginTop = '12px'; applyBtn.style.padding = '10px 0'; applyBtn.style.borderRadius = '6px'; applyBtn.style.border = 'none'; applyBtn.style.backgroundColor = '#28a745'; applyBtn.style.color = '#fff'; applyBtn.style.fontWeight = 'bold'; applyBtn.style.cursor = 'pointer'; applyBtn.style.userSelect = 'none'; applyBtn.onmouseenter = () => applyBtn.style.backgroundColor = '#218838'; applyBtn.onmouseleave = () => applyBtn.style.backgroundColor = '#28a745'; panel.appendChild(applyBtn); document.body.appendChild(container); // Synchronize buttons with current URL function syncButtonsWithUrl() { const urlTags = new URLSearchParams(window.location.search).getAll('excludedTag'); const urlSet = new Set(urlTags); excludeTags.forEach(tag => { const btn = [...btnContainer.children].find(b => b.dataset.tag === tag); if (!btn) return; if (urlSet.has(tag)) { btn.dataset.selected = 'true'; btn.style.backgroundColor = '#d9534f'; } else { btn.dataset.selected = 'false'; btn.style.backgroundColor = '#2e3a4e'; } }); } syncButtonsWithUrl(); // Action apply applyBtn.onclick = () => { const selectedTags = []; for (const btn of btnContainer.children) { if (btn.dataset.selected === 'true') { selectedTags.push(btn.dataset.tag); } } // Save localStorage.setItem(STORAGE_KEY, JSON.stringify(selectedTags)); // Modifier URL et reload si besoin const url = new URL(window.location.href); url.searchParams.delete('excludedTag'); selectedTags.forEach(t => url.searchParams.append('excludedTag', t)); if (url.toString() !== window.location.href) { window.location.href = url.toString(); } }; // Expose sync for external usage return { syncButtonsWithUrl, }; } // Managing URL changes in SPA (pushState + popstate) function hookUrlChange(callback) { let lastUrl = location.href; new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; callback(); } }).observe(document, {subtree: true, childList: true}); } // When loading, apply the stored tags (reload if necessary) if (applyStoredTagsInUrl()) return; // reload lancé => stop ici // Creation UI const ui = createUI(); // Sur changement d'URL détecté, appliquer tags + resync UI hookUrlChange(() => { if (applyStoredTagsInUrl()) return; // reload if tags missing // Else update buttons with URL ui.syncButtonsWithUrl(); }); })();