您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ignore an unlimited amount of users with other custom settings: deleting the entire post, replace the content of the message with a custom message, replace or delete the avatar, keep or delete the signature.
当前为
// ==UserScript== // @name Unlimited MAL Ignore list // @namespace http://tampermonkey.net/ // @version 0.60 // @description Ignore an unlimited amount of users with other custom settings: deleting the entire post, replace the content of the message with a custom message, replace or delete the avatar, keep or delete the signature. // @author Only_Brad // @match https://myanimelist.net/* // @run-at document-end // ==/UserScript== (function() { const POSTS_URL = "forum/?topicid"; const TOPICS_URL = "forum/?board"; const CLUB_TOPICS_URL_1 = "clubs.php"; const CLUB_TOPICS_URL_2 = "forum/?clubid"; const BLACKLIST_URL = "https://myanimelist.net/blacklist"; const BLACKLIST_KEY = "ignore-list"; const SETTINGS_KEY = "ignore-list-settings"; const POST_USERS_SELECTOR = ".forum_boardrow2 > div"; const TOPIC_USERS_SELECTOR = ".forum_postusername a"; const MESSAGE_SELECTOR = ".forum_boardrow1 [id^=message]"; const AVATAR_SELECTOR = ".forum-icon"; const USER_PROFILE_SELECTOR = "[href^='/profile']"; const USER_INFO_SELECTOR = "[id^=messageuser]"; const SIGNATURE_SELECTOR = ".sig"; const FORUM_MESSAGE_SELECTOR = "[id^=forumMsg]"; const ACTION_BAR_SELECTOR = "[id^=postEditButtons]"; const IGNORE_POSTS = 0; const REPLACE_POSTS = 1; const DO_NOTHING_POSTS = 2; let blacklist; let settings; //routing if (window.location.href.includes(POSTS_URL)) { handlePosts(); } else if ( window.location.href.includes(TOPICS_URL) || window.location.href.includes(CLUB_TOPICS_URL_1) || window.location.href.includes(CLUB_TOPICS_URL_2) ) { handleTopics(); } else if (window.location.href === BLACKLIST_URL) { handleBlacklist(); } //GM_addStyle equivalent that works on firefox function addStyle(css) { const style = document.getElementById("addStyleBy8626") || (function() { const style = document.createElement('style'); style.type = 'text/css'; style.id = "addStyleBy8626"; document.head.appendChild(style); return style; })(); style.innerHTML += css; } //helper functions to load from localStorage function loadBlackList() { blacklist = JSON.parse(localStorage.getItem(BLACKLIST_KEY)) || []; } function saveBlackList() { localStorage.setItem(BLACKLIST_KEY, JSON.stringify(blacklist)); } function loadSettings() { settings = JSON.parse(localStorage.getItem(SETTINGS_KEY)) || { replaceAvatar: false, removeSignatures: true, postMode: IGNORE_POSTS, removeTopics: true, customPost: "", customAvatar: "" }; } function saveSetting(key, value) { settings[key] = value; localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); } //functions called by the routers function handlePosts() { loadBlackList(); loadSettings(); switch (settings.postMode) { case IGNORE_POSTS: ignorePosts(); return; case REPLACE_POSTS: replacePosts(); break; default: break; } addBlackListButtons(); if (settings.replaceAvatar) replaceAvatar(); if (settings.removeSignatures) removeSignatures(); } function handleTopics() { loadBlackList(); loadSettings(); if (settings.removeTopics) removeTopics(); } function handleBlacklist() { loadBlackList(); loadSettings(); document.title = "Blacklist - MyAnimeList.net"; //remove the 404 stuff document.querySelector("h1").textContent = "Ignore List"; document.querySelector(".error404").remove(); //CSS addStyle(".user{display:flex;margin:10px}.name{margin-right:20px}.name{border-bottom:solid #000 1px}.name[contenteditable]{min-width:100px;border-bottom:solid #000 1px}.name[contenteditable]:focus{border:none;outline:solid red 5px}.page-common #content{display:flex;justify-content:center;}.settings{display:flex;gap:25px;}.settings>*{padding: 25px;}.customPost{width:100% !important;}"); //HTML for the blacklist document.getElementById("content").innerHTML = `<div data-blacklist class="black-list"></div> <div data-add-user class="add-user"> <div data-user class="user"> <div data-name class="name" contenteditable="true" onclick="this.focus()"></div> <button data-add class="add">Add</div> </div> </div>` //HTML for the settings const settings = document.createElement("div"); settings.innerHTML = ` <h2>Settings</h2> <form> <div class="settings"> <div class="posts"> <h3>Posts</h3> <div class="form-check"> <input class="form-check-input" type="radio" name="posts" id="doNothingPosts" data-clickable-setting="doNothingPosts"> <label class="form-check-label" for="doNothingPosts"> Do Nothing </label> </div> <div class="form-check"> <input class="form-check-input" type="radio" name="posts" id="hidePosts" data-clickable-setting="hidePosts"> <label class="form-check-label" for="hidePosts"> Hide Posts </label> </div> <div class="form-check"> <input class="form-check-input" type="radio" name="posts" id="replacePosts" data-clickable-setting="replacePosts"> <label class="form-check-label" for="replacePosts"> Replace posts with a custom message </label> </div> <textarea class="form-control customPost" name="customPost" id="customPost" data-text-setting="customPost"></textarea> </div> <div class="avatar"> <h3>Posts extra options</h3> <div class="form-check"> <input class="form-check-input" type="checkbox" name="replaceAvatar" id="replaceAvatar" data-clickable-setting="replaceAvatar"> <label class="form-check-label" for="replaceAvatar"> Replace avatars with a custom avatar </label> <input class="form-control" type="text" name="customAvatar" id="customAvatar" data-text-setting="customAvatar"> <br> <small>Leave it empty to remove the avatar</small> </div> <div class="form-check" style="margin-top: 10px;"> <input class="form-check-input" type="checkbox" name="removeSignatures" id="removeSignatures" data-clickable-setting="removeSignatures"> <label class="form-check-label" for="removeSignatures"> Hide the signature </label> </div> <small style="margin-top: 20px; display: block;"><strong>These settings have no effect if the Posts setting is set to "Hide Posts"</strong></small> </div> <div class="topics"> <h3>Topics</h3> <div class="form-check"> <input class="form-check-input" type="checkbox" name="removeTopics" id="removeTopics" data-clickable-setting="removeTopics"> <label class="form-check-label" for="removeTopics"> Hide Topics </label> </div> </div> </div> </form>`; document.getElementById("content").insertAdjacentElement("afterend", settings); document.querySelector("[data-add]").addEventListener("click", addNode); document.querySelectorAll("[data-clickable-setting]").forEach(clickable => { clickable.addEventListener("click", clickedSetting); }); document.querySelectorAll("[data-text-setting]").forEach(text => { text.addEventListener("input", textSetting); }); blacklist.forEach(createNode); loadSettingsIntoInputs(); } function clickedSetting(e) { const input = e.target; switch (input.dataset.clickableSetting) { case "doNothingPosts": saveSetting("postMode", DO_NOTHING_POSTS); break; case "hidePosts": saveSetting("postMode", IGNORE_POSTS); break; case "replacePosts": saveSetting("postMode", REPLACE_POSTS); break; case "replaceAvatar": saveSetting("replaceAvatar", input.checked); break; case "removeTopics": saveSetting("removeTopics", input.checked); break; case "removeSignatures": saveSetting("removeSignatures", input.checked); break; default: return; } } function textSetting(e) { const input = e.target; switch (input.dataset.textSetting) { case "customPost": saveSetting("customPost", input.value); break; case "customAvatar": saveSetting("customAvatar", input.value); break; default: return; } } function loadSettingsIntoInputs() { switch (settings.postMode) { case DO_NOTHING_POSTS: document.getElementById("doNothingPosts").checked = true; break; case IGNORE_POSTS: document.getElementById("hidePosts").checked = true; break; case REPLACE_POSTS: document.getElementById("replacePosts").checked = true; break; } if (settings.removeTopics) { document.getElementById("removeTopics").checked = true; } if (settings.removeSignatures) { document.getElementById("removeSignatures").checked = true; } document.getElementById("customPost").value = settings.customPost || ""; document.getElementById("customAvatar").value = settings.customAvatar || ""; } function alterPosts(action) { document.querySelectorAll(POST_USERS_SELECTOR).forEach(user => { if (!blacklist.includes(user.querySelector("strong").textContent)) return; let post = user.parentNode; for (let i = 0; i < 4; i++) { post = post.parentNode; } action(post, user); }); } function removeTopics() { document.querySelectorAll(TOPIC_USERS_SELECTOR).forEach(user => { if (!blacklist.includes(user.textContent)) return; user.closest("tr").style.display = "none"; }); } function ignorePosts() { alterPosts(post => { post.style.display = "none"; post.previousElementSibling.style.display = "none"; }); } function replacePosts() { alterPosts(post => { const message = post.querySelector(MESSAGE_SELECTOR); message.innerHTML = settings.customPost; }); } function replaceAvatar() { alterPosts((post, user) => { const avatar = user.querySelector(AVATAR_SELECTOR); if (!avatar) { if (settings.customAvatar === "") return; const avatar = document.createElement("a"); avatar.href = user.querySelector(USER_PROFILE_SELECTOR).href avatar.className = "forum-icon"; avatar.innerHTML = ` <img class=" lazyloaded" data-src="${settings.customAvatar}" vspace="2" border="0" src="${settings.customAvatar}" width="100" height="125">`; user.querySelector(USER_INFO_SELECTOR).insertAdjacentElement('afterend', avatar); } else { if (settings.customAvatar === "") { avatar.style.display = "none"; return; } const img = avatar.querySelector("img"); img.src = settings.customAvatar; img.setAttribute("data-src", settings.customAvatar); img.setAttribute("width", 100); img.setAttribute("height", 125); } }); } function removeSignatures() { alterPosts(post => { const signature = post.querySelector(SIGNATURE_SELECTOR); if (!signature) return; signature.style.display = "none"; }); } function addBlackListButtons() { document.querySelectorAll(FORUM_MESSAGE_SELECTOR).forEach(forumMessage => { const actionBar = forumMessage.querySelector(ACTION_BAR_SELECTOR); const a = document.createElement("a"); a.href = "#!"; a.textContent = "Blacklist User"; a.dataset.username = forumMessage.querySelector("strong").textContent; a.onclick = blacklistUser; actionBar.prepend(document.createTextNode(" - ")); actionBar.prepend(a); }); } function blacklistUser(e) { addUser(e.target.dataset.username); window.location.reload(); } //Add a user to the blacklist if its not already there function addUser(username) { blacklist.push(username); saveBlackList(); } //Remove a user from the blacklist if it's there function removeUser(userName) { blacklist = blacklist.filter(name => userName !== name); saveBlackList(); } //remove the user node from the html code and then update the localStorage function removeNode(e) { const row = e.target.parentNode; const name = row.querySelector("[data-name]").textContent; row.remove(); removeUser(name); } //modify the user node from the html code and then update the localStorage function saveNode(e) { const newName = e.target.textContent; const previousName = e.target.dataset.previousName; previousName && removeUser(previousName); if (newName !== "") { addUser(newName); e.target.dataset.previousName = newName; } else { e.target.parentNode.remove(); } } //add a new user node to the html code and then update the localStorage function addNode(e) { const node = e.target.parentNode; const usernameNode = node.querySelector("[data-name]"); const username = usernameNode.textContent; usernameNode.textContent = ""; if (!blacklist.includes(username)) { createNode(username); addUser(username); } } //create the user node then add it the html code function createNode(username) { const newUser = document.createElement("div"); newUser.setAttribute("data-user", ""); newUser.className = "user"; newUser.innerHTML = `<div data-name class="name" contenteditable="true" onclick="this.focus()" data-previous-name="${username}">${username}</div> <button data-remove class="remove">Remove</button>`; newUser.querySelector("[data-name]").addEventListener("focusout", saveNode); newUser.querySelector("[data-remove]").addEventListener("click", removeNode); document.querySelector("[data-blacklist]").append(newUser); } })();