// ==UserScript== // @name NGA Filter // @namespace https://greasyfork.org/users/263018 // @version 1.0.3 // @author snyssss // @description troll must die // @match *bbs.nga.cn/thread.php?fid=* // @match *bbs.nga.cn/read.php?tid=* // @match *bbs.nga.cn/nuke.php?* // @match *ngabbs.com/thread.php?fid=* // @match *ngabbs.com/read.php?tid=* // @match *ngabbs.com/nuke.php?* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @noframes // @downloadURL none // ==/UserScript== (n => { "use strict"; if (n === undefined) return; const key = "NGAFilter"; // 数据 const data = (() => { const d = { tags: {}, users: {}, options: { filterMode: 0, keyword: "" } }; const v = GM_getValue(key); if (typeof v !== "object") { return d; } return Object.assign(d, v); })(); // 保存数据 const saveData = () => { GM_setValue(key, data); }; // 增加标记 const addTag = name => { const tag = Object.values(data.tags).find(item => item.name === name); if (tag) return tag.id; const id = Math.max(...Object.values(data.tags).map(item => item.id), 0) + 1; const hash = (() => { let h = 5381; for (var i = 0; i < name.length; i++) { h = ((h << 5) + h + name.charCodeAt(i)) & 0xffffffff; } return h; })(); const hex = Math.abs(hash).toString(16) + "000000"; const hsv = [ `0x${hex.substr(2, 2)}` / 255, `0x${hex.substr(2, 2)}` / 255 / 2 + 0.25, `0x${hex.substr(4, 2)}` / 255 / 2 + 0.25 ]; const rgb = n.hsvToRgb(hsv[0], hsv[1], hsv[2]); const color = ["#", ...rgb].reduce((a, b) => { return a + ("0" + b.toString(16)).slice(-2); }); data.tags[id] = { id, name, color, enabled: true }; saveData(); return id; }; // 增加用户 const addUser = (id, name = null, tags = [], isEnabled = true) => { if (data.users[id]) return data.users[id]; data.users[id] = { id, name, tags, enabled: isEnabled }; saveData(); return data.users[id]; }; // 旧版本数据迁移 { const dataKey = "troll_data"; const modeKey = "troll_mode"; const keywordKey = "troll_keyword"; if (localStorage.getItem(dataKey)) { let trollMap = (function() { try { return JSON.parse(localStorage.getItem(dataKey)) || {}; } catch (e) {} return {}; })(); let filterMode = ~~localStorage.getItem(modeKey); let filterKeyword = localStorage.getItem(keywordKey) || ""; // 整理标签 [...new Set(Object.values(trollMap).flat())].forEach(item => addTag(item) ); // 整理用户 Object.keys(trollMap).forEach(item => { addUser( item, null, trollMap[item].map(tag => addTag(tag)) ); }); data.options.filterMode = filterMode ? 0 : 1; data.options.keyword = filterKeyword; localStorage.removeItem(dataKey); localStorage.removeItem(modeKey); localStorage.removeItem(keywordKey); saveData(); } } // 编辑用户标记 const editUser = (() => { let window; return (uid, name, callback) => { if (window === undefined) { window = n.createCommmonWindow(); } const user = data.users[uid]; const content = document.createElement("div"); content.className = "w100"; content.innerHTML = ` ${Object.values(data.tags) .map( tag => ` ` ) .join("")}
${ tag.name } item === tag.id) && "checked"}/>
`; const actions = content.getElementsByTagName("button"); actions[0].onclick = () => { actions[0].innerText = actions[0].innerText === "禁用" ? "启用" : "禁用"; }; actions[1].onclick = () => { if (confirm("是否确认?")) { delete data.users[uid]; saveData(); callback && callback(); window._.hide(); } }; actions[2].onclick = () => { if (confirm("是否确认?")) { const values = [...content.getElementsByTagName("input")]; const newTags = values[values.length - 1].value .split("|") .filter(item => item.length) .map(item => addTag(item)); const tags = [ ...new Set( values .filter(item => item.type === "checkbox" && item.checked) .map(item => ~~item.value) .concat(newTags) ) ].sort(); if (user) { user.tags = tags; user.enabled = actions[0].innerText === "禁用"; } else { addUser(uid, name, tags, actions[0].innerText === "禁用"); } saveData(); callback && callback(); window._.hide(); } }; if (user === undefined) { actions[1].style = "display: none;"; } window._.addContent(null); window._.addTitle(`编辑标记 - ${name ? name : "#" + uid}`); window._.addContent(content); window._.show(); }; })(); // 过滤 const reFilter = (() => { const tPage = location.pathname === "/thread.php"; const pPage = location.pathname === "/read.php"; const uPage = location.pathname === "/nuke.php"; const func = (() => { if (tPage) { return () => { const tData = n.topicArg.data; Object.values(tData).forEach(item => { const uid = item[2].search.match(/uid=(\S+)/) && item[2].search.match(/uid=(\S+)/)[1]; const user = data.users[uid]; const tags = user ? user.tags.map(tag => data.tags[tag]) : []; const isBlock = (user && user.enabled && (tags.length === 0 || tags.filter(tag => tag.enabled).length)) || (data.options.keyword.length && item[1].innerText.search(data.options.keyword) >= 0); item.contentC = item[1]; item.contentB = item.contentB || item.contentC.innerHTML; item.containerC = item.containerC || item.contentC.parentNode.parentNode; item.containerC.style = isBlock && data.options.filterMode === 0 ? "display: none;" : ""; item.contentC.style = isBlock && data.options.filterMode === 1 ? "text-decoration: line-through;" : ""; }); }; } else if (pPage) { return () => { const pData = n.postArg.data; Object.values(pData).forEach(item => { if (typeof item.i === "number") { item.actionC = item.actionC || (() => { const ele = item.uInfoC.firstElementChild.lastElementChild; ele.onclick = null; return ele; })(); item.tagC = item.tagC || (() => { const tc = document.createElement("div"); tc.className = "filter-tags"; item.uInfoC.appendChild(tc); return tc; })(); } item.pName = item.pName || item.uInfoC.getElementsByClassName("author")[0].innerText; item.reFilter = item.reFilter || (() => { const user = data.users[item.pAid]; const tags = user ? user.tags.map(tag => data.tags[tag]) : []; const isBlock = user && user.enabled && (tags.length === 0 || tags.filter(tag => tag.enabled).length); item.avatarC = item.avatarC || (() => { const tc = document.createElement("div"); const avatar = document.getElementById( `posteravatar${item.i}` ); if (avatar) { avatar.parentNode.insertBefore(tc, avatar.nextSibling); tc.appendChild(avatar); } return tc; })(); item.contentB = item.contentB || item.contentC.innerHTML; item.containerC = item.containerC || (() => { let temp = item.contentC; while ( temp.className !== "forumbox postbox" && temp.className !== "comment_c left" ) { temp = temp.parentNode; } return temp; })(); item.containerC.style.display = isBlock && data.options.filterMode === 0 ? "none" : ""; item.contentC.innerHTML = isBlock && data.options.filterMode === 1 ? `
Troll must die. 点击查看
${item.contentB}
` : item.contentB; item.avatarC.style.display = isBlock ? "none" : ""; if (item.actionC) { item.actionC.style = user && user.enabled ? "background: #cb4042;" : "background: #aaa;"; } if (item.tagC) { item.tagC.style.display = tags.length ? "" : "none"; item.tagC.innerHTML = tags .map( tag => `${tag.name}` ) .join(""); } }); if (item.actionC) { item.actionC.onclick = item.actionC.onclick || (e => { if (item.pAid < 0) return; const user = data.users[item.pAid]; if (e.ctrlKey) { editUser(item.pAid, item.pName, item.reFilter); } else { if (user) { if (user.tags.length) { user.enabled = !user.enabled; user.name = item.pName; } else { delete data.users[user.id]; } } else { addUser(item.pAid, item.pName); } saveData(); item.reFilter(); } }); } item.reFilter(); }); }; } else if (uPage) { return () => { const container = document.getElementById("ucp_block"); if (container.firstChild) { const uid = container.innerText.match(/用户ID\s*:\s*(\S+)/)[1]; const name = container.innerText.match(/用户名\s*:\s*(\S+)/)[1]; container.tagC = container.tagC || (() => { const c = document.createElement("span"); c.innerHTML = `

:: ${name} 的标记 ::

`; c.getElementsByTagName("a")[0].onclick = () => { editUser(uid, name, container.refresh); }; container.firstChild.insertBefore( c, container.firstChild.childNodes[1] ); return c.getElementsByClassName("filter-tags")[0]; })(); container.refresh = () => { container.tagC.innerHTML = data.users[uid].tags .map( tag => `${data.tags[tag].name}` ) .join(""); }; container.refresh(); } }; } return () => {}; })(); const observer = new MutationObserver(mutations => { if (mutations.find(mutation => mutation.addedNodes.length)) { func(); } }); if (tPage) { observer.observe(document.getElementById("topicrows"), { childList: true }); } else if (pPage) { observer.observe(document.getElementById("m_posts_c"), { childList: true }); } else if (uPage) { observer.observe(document.getElementById("ucp_block"), { childList: true }); } func(); return func; })(); // STYLE GM_addStyle(` .filter-tags { margin: 2px -0.2em 0; text-align: left; } .filter-table { border: 1px solid #ead5bc; border-left: none; border-bottom: none; width: 99.95%; } .filter-table thead { background-color: #591804; color: #fff8e7; } .filter-table tbody tr { background-color: #fff0cd; } .filter-table tbody tr:nth-of-type(odd) { background-color: #fff8e7; } .filter-table td { border: 1px solid #ead5bc; border-top: none; border-right: none; padding: 6px; } `); // UI const u = (() => { const modules = {}; const tabContainer = (() => { const c = document.createElement("div"); c.className = "w100"; c.innerHTML = `
`; return c; })(); const tabPanelContainer = (() => { const c = document.createElement("div"); c.style = "min-width: 20vw; max-width: 80vw; max-height: 80vh; overflow: auto;"; c.innerHTML = ` `; return c; })(); const content = (() => { const c = document.createElement("div"); c.append(tabContainer); c.append(tabPanelContainer); return c; })(); const addModule = (() => { const tc = tabContainer.getElementsByTagName("tr")[0]; const cc = tabPanelContainer; return module => { const tabBox = document.createElement("td"); tabBox.innerHTML = `${module.name}`; const tab = tabBox.childNodes[0]; const toggle = () => { Object.values(modules).forEach(item => { if (item.tab === tab) { item.tab.className = "nobr"; item.content.style = "display: block"; item.refresh(); } else { item.tab.className = "nobr silver"; item.content.style = "display: none"; } }); }; tc.append(tabBox); cc.append(module.content); tab.onclick = toggle; modules[module.name] = { ...module, tab, toggle }; return modules[module.name]; }; })(); return { content, modules, addModule }; })(); // 屏蔽列表 const blockModule = (() => { const content = (() => { const c = document.createElement("div"); c.style = "display: none"; c.innerHTML = `
昵称 标记 操作
`; return c; })(); const refresh = (() => { const container = content.getElementsByTagName("tbody")[0]; const func = () => { container.innerHTML = ""; Object.values(data.users).forEach(item => { const tc = document.createElement("tr"); tc.refresh = () => { if (data.users[item.id]) { tc.innerHTML = ` [${ item.name ? "@" + item.name : "#" + item.id }] ${item.tags .map(tag => { if (data.tags[tag]) { return `${data.tags[tag].name}`; } }) .join("")} `; const actions = tc.getElementsByTagName("button"); actions[0].onclick = () => { editUser(item.id, item.name, tc.refresh); }; actions[1].onclick = () => { data.users[item.id].enabled = !data.users[item.id].enabled; actions[1].innerHTML = data.users[item.id].enabled ? "禁用" : "启用"; saveData(); reFilter(); }; actions[2].onclick = () => { if (confirm("是否确认?")) { delete data.users[item.id]; container.removeChild(tc); saveData(); reFilter(); } }; } else { tc.remove(); } }; tc.refresh(); container.appendChild(tc); }); }; return func; })(); return { name: "屏蔽列表", content, refresh }; })(); // 标记设置 const tagModule = (() => { const content = (() => { const c = document.createElement("div"); c.style = "display: none"; c.innerHTML = `
标记 列表 操作
`; return c; })(); const refresh = (() => { const container = content.getElementsByTagName("tbody")[0]; const func = () => { container.innerHTML = ""; Object.values(data.tags).forEach(item => { const tc = document.createElement("tr"); tc.innerHTML = ` ${item.name}
${Object.values(data.users) .filter(user => user.tags.find(tag => tag === item.id) ) .map( user => `[${ user.name ? "@" + user.name : "#" + user.id }]` ) .join("")}
`; const actions = tc.getElementsByTagName("button"); actions[0].onclick = (() => { let hide = true; return () => { hide = !hide; actions[0].nextElementSibling.style = `display: ${ hide ? "none" : "block" };`; }; })(); actions[1].onclick = () => { data.tags[item.id].enabled = !data.tags[item.id].enabled; actions[1].innerHTML = data.tags[item.id].enabled ? "禁用" : "启用"; saveData(); reFilter(); }; actions[2].onclick = () => { if (confirm("是否确认?")) { delete data.tags[item.id]; Object.values(data.users).forEach(user => { const index = user.tags.findIndex(tag => tag === item.id); if (index >= 0) { user.tags.splice(index, 1); } }); container.removeChild(tc); saveData(); reFilter(); } }; container.appendChild(tc); }); }; return func; })(); return { name: "标记设置", content, refresh }; })(); // 通用设置 const commonModule = (() => { const content = (() => { const c = document.createElement("div"); c.style = "display: none"; return c; })(); const refresh = (() => { const container = content; const func = () => { container.innerHTML = ""; // 屏蔽关键词 { const tc = document.createElement("div"); tc.innerHTML += `
过滤关键词,用"|"隔开
`; const actions = tc.getElementsByTagName("button"); actions[0].onclick = () => { const v = actions[0].previousElementSibling.value; data.options.keyword = v; saveData(); reFilter(); }; container.appendChild(tc); } // 过滤方式 { const tc = document.createElement("div"); tc.innerHTML += `
过滤方式
隐藏 标记
`; const actions = tc.getElementsByTagName("button"); actions[0].onclick = () => { const values = document.getElementsByName("filterType"); for (let i = 0, length = values.length; i < length; i++) { if (values[i].checked) { data.options.filterMode = i; break; } } saveData(); reFilter(); }; container.appendChild(tc); } // 删除没有标记的用户 { const tc = document.createElement("div"); tc.innerHTML += `
`; const actions = tc.getElementsByTagName("button"); actions[0].onclick = () => { if (confirm("是否确认?")) { Object.values(data.users).forEach(item => { if (item.tags.length === 0) { delete data.users[item.id]; } }); saveData(); reFilter(); } }; container.appendChild(tc); } // 删除没有用户的标记 { const tc = document.createElement("div"); tc.innerHTML += `
`; const actions = tc.getElementsByTagName("button"); actions[0].onclick = () => { if (confirm("是否确认?")) { Object.values(data.tags).forEach(item => { if ( Object.values(data.users).filter(user => user.tags.find(tag => tag === item.id) ).length === 0 ) { delete data.tags[item.id]; } }); saveData(); reFilter(); } }; container.appendChild(tc); } }; return func; })(); return { name: "通用设置", content, refresh }; })(); u.addModule(blockModule).toggle(); u.addModule(tagModule); u.addModule(commonModule); // 增加菜单项 (() => { const title = "屏蔽/标记"; let window; n.mainMenu.addItemOnTheFly(title, null, () => { if (window === undefined) { window = n.createCommmonWindow(); } window._.addContent(null); window._.addTitle(title); window._.addContent(u.content); window._.show(); }); })(); })(commonui);