您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
NGA 屏蔽插件,支持用户、标记、关键字、属地、小号、流量号、低声望、匿名过滤。troll must die。
当前为
- // ==UserScript==
- // @name NGA Filter
- // @namespace https://greasyfork.org/users/263018
- // @version 1.12.0
- // @author snyssss
- // @description NGA 屏蔽插件,支持用户、标记、关键字、属地、小号、流量号、低声望、匿名过滤。troll must die。
- // @license MIT
- // @match *://bbs.nga.cn/*
- // @match *://ngabbs.com/*
- // @match *://nga.178.com/*
- // @grant GM_addStyle
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_registerMenuCommand
- // @noframes
- // ==/UserScript==
- ((n, self) => {
- if (n === undefined) return;
- // KEY
- const DATA_KEY = "NGAFilter";
- const USER_AGENT_KEY = "USER_AGENT_KEY";
- // User Agent
- const USER_AGENT = (() => {
- const data = GM_getValue(USER_AGENT_KEY) || "Nga_Official";
- GM_registerMenuCommand(`修改UA:${data}`, () => {
- const value = prompt("修改UA", data);
- if (value) {
- GM_setValue(USER_AGENT_KEY, value);
- location.reload();
- }
- });
- return data;
- })();
- // 简单的统一请求
- const request = (url, config = {}) =>
- fetch(url, {
- headers: {
- "X-User-Agent": USER_AGENT,
- },
- ...config,
- });
- // 过滤提示
- const FILTER_TIPS =
- "过滤顺序:用户 > 标记 > 关键字 > 属地<br/>过滤级别:隐藏 > 遮罩 > 标记 > 继承 > 显示<br/>相同类型按最高级别过滤";
- // 过滤方式
- const FILTER_MODE = ["继承", "标记", "遮罩", "隐藏", "显示"];
- // 切换过滤方式
- const switchFilterMode = (value) => {
- const next = FILTER_MODE.indexOf(value) + 1;
- if (next >= FILTER_MODE.length) {
- return FILTER_MODE[0];
- }
- return FILTER_MODE[next];
- };
- // 数据
- const data = (() => {
- const d = {
- tags: {},
- users: {},
- keywords: {},
- locations: {},
- options: {
- filterRegdateLimit: 0,
- filterPostnumLimit: 0,
- filterTopicRateLimit: 100,
- filterReputationLimit: NaN,
- filterAnony: false,
- filterMode: "隐藏",
- },
- };
- const v = GM_getValue(DATA_KEY);
- if (typeof v !== "object") {
- return d;
- }
- return Object.assign(d, v);
- })();
- // 保存数据
- const saveData = () => {
- GM_setValue(DATA_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.substring(2, 4)}` / 255,
- `0x${hex.substring(2, 4)}` / 255 / 2 + 0.25,
- `0x${hex.substring(4, 6)}` / 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,
- filterMode: FILTER_MODE[0],
- };
- saveData();
- return id;
- };
- // 增加用户
- const addUser = (id, name = null, tags = [], filterMode = FILTER_MODE[0]) => {
- if (data.users[id]) return data.users[id];
- data.users[id] = {
- id,
- name,
- tags,
- filterMode,
- };
- saveData();
- return data.users[id];
- };
- // 增加关键字
- const addKeyword = (
- keyword,
- filterMode = FILTER_MODE[0],
- filterLevel = 0
- ) => {
- const id =
- Math.max(...Object.values(data.keywords).map((item) => item.id), 0) + 1;
- data.keywords[id] = {
- id,
- keyword,
- filterMode,
- filterLevel,
- };
- saveData();
- return id;
- };
- // 增加属地
- const addLocation = (keyword, filterMode = FILTER_MODE[0]) => {
- const id =
- Math.max(...Object.values(data.locations).map((item) => item.id), 0) + 1;
- data.locations[id] = {
- id,
- keyword,
- filterMode,
- };
- saveData();
- return 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,
- (typeof trollMap[item] === "object" ? trollMap[item] : []).map(
- (tag) => addTag(tag)
- )
- );
- });
- data.options.filterMode = filterMode ? "隐藏" : "标记";
- data.options.keyword = filterKeyword;
- localStorage.removeItem(dataKey);
- localStorage.removeItem(modeKey);
- localStorage.removeItem(keywordKey);
- saveData();
- }
- // v1.1.0 -> v1.1.1
- {
- Object.values(data.users).forEach(({ id, name, tags, enabled }) => {
- if (enabled !== undefined) {
- data.users[id] = {
- id,
- name,
- tags,
- filterMode: enabled ? "继承" : "显示",
- };
- }
- });
- Object.values(data.tags).forEach(({ id, name, color, enabled }) => {
- if (enabled !== undefined) {
- data.tags[id] = {
- id,
- name,
- color,
- filterMode: enabled ? "继承" : "显示",
- };
- }
- });
- if (data.options.filterMode === 0) {
- data.options.filterMode = "隐藏";
- } else if (data.options.filterMode === 1) {
- data.options.filterMode = "标记";
- }
- saveData();
- }
- // v1.2.x -> v1.3.0
- {
- if (data.options.keyword) {
- addKeyword(data.options.keyword);
- delete data.options.keyword;
- 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");
- const size = Math.floor((screen.width * 0.8) / 200);
- const items = Object.values(data.tags).map(
- (tag, index) => `
- <td class="c1">
- <label for="s-tag-${index}" style="display: block; cursor: pointer;">
- <b class="block_txt nobr" style="background:${
- tag.color
- }; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>
- </label>
- </td>
- <td class="c2" width="1">
- <input id="s-tag-${index}" type="checkbox" value="${tag.id}" ${
- user && user.tags.find((item) => item === tag.id) && "checked"
- }/>
- </td>
- `
- );
- const rows = [...new Array(Math.ceil(items.length / size))].map(
- (item, index) =>
- `
- <tr class="row${(index % 2) + 1}">
- ${items.slice(size * index, size * (index + 1)).join("")}
- </tr>
- `
- );
- content.className = "w100";
- content.innerHTML = `
- <div class="filter-table-wrapper" style="width: 80vw;">
- <table class="filter-table forumbox">
- <tbody>
- ${rows.join("")}
- </tbody>
- </table>
- </div>
- <div style="margin: 10px 0;">
- <input placeholder="一次性添加多个标记用"|"隔开,不会添加重名标记" style="width: -webkit-fill-available;" />
- </div>
- <div style="margin: 10px 0;">
- <span>过滤方式:</span>
- <button>${(user && user.filterMode) || FILTER_MODE[0]}</button>
- <div class="right_">
- <button>删除</button>
- <button>保存</button>
- </div>
- </div>
- <div class="silver" style="margin-top: 5px;">${FILTER_TIPS}</div>
- `;
- const actions = content.getElementsByTagName("button");
- actions[0].onclick = () => {
- actions[0].innerText = switchFilterMode(
- actions[0].innerText || FILTER_MODE[0]
- );
- };
- 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.filterMode = 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 witchHunter = (() => {
- const key = "WITCH_HUNTER";
- const data = GM_getValue(key) || {};
- const add = async (fid, label) => {
- if (Object.values(data).find((item) => item.fid === fid)) {
- alert("已有相同版面ID");
- return;
- }
- const info = await new Promise((resolve) => {
- request(`/thread.php?lite=js&fid=${fid}`)
- .then((res) => res.blob())
- .then((blob) => {
- const reader = new FileReader();
- reader.onload = () => {
- const text = reader.result;
- const result = JSON.parse(
- text.replace("window.script_muti_get_var_store=", "")
- );
- resolve(result.data);
- };
- reader.readAsText(blob, "GBK");
- })
- .catch(() => {
- resolve({});
- });
- });
- if (info.__F === undefined) {
- alert("版面ID有误");
- return;
- }
- const name = info.__F.name;
- const id = Math.max(...Object.values(data).map((item) => item.id), 0) + 1;
- const hash = (() => {
- let h = 5381;
- for (var i = 0; i < label.length; i++) {
- h = ((h << 5) + h + label.charCodeAt(i)) & 0xffffffff;
- }
- return h;
- })();
- const hex = Math.abs(hash).toString(16) + "000000";
- const hsv = [
- `0x${hex.substring(2, 4)}` / 255,
- `0x${hex.substring(2, 4)}` / 255 / 2 + 0.25,
- `0x${hex.substring(4, 6)}` / 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[id] = {
- id,
- fid,
- name,
- label,
- color,
- };
- GM_setValue(key, data);
- };
- const remove = (id) => {
- delete data[id];
- GM_setValue(key, data);
- };
- const run = (uid, element) => {
- if (uid < 0) {
- return;
- }
- Promise.all(
- Object.values(data).map(async (item) => {
- const api = `/thread.php?lite=js&fid=${item.fid}&authorid=${uid}`;
- const verify =
- (await new Promise((resolve) => {
- request(api)
- .then((res) => res.blob())
- .then((blob) => {
- const reader = new FileReader();
- reader.onload = () => {
- const text = reader.result;
- const result = JSON.parse(
- text.replace("window.script_muti_get_var_store=", "")
- );
- if (result.error) {
- resolve(false);
- return;
- }
- resolve(true);
- };
- reader.readAsText(blob, "GBK");
- })
- .catch(() => {
- resolve(false);
- });
- })) ||
- (await new Promise((resolve) => {
- request(`${api}&searchpost=1`)
- .then((res) => res.blob())
- .then((blob) => {
- const reader = new FileReader();
- reader.onload = () => {
- const text = reader.result;
- const result = JSON.parse(
- text.replace("window.script_muti_get_var_store=", "")
- );
- if (result.error) {
- resolve(false);
- return;
- }
- resolve(true);
- };
- reader.readAsText(blob, "GBK");
- })
- .catch(() => {
- resolve(false);
- });
- }));
- if (verify) {
- return item;
- }
- })
- )
- .then((res) => res.filter((item) => item))
- .then((res) => {
- res
- .filter(
- (current, index) =>
- res.findIndex((item) => item.label === current.label) === index
- )
- .forEach((item) => {
- element.style.display = "block";
- element.innerHTML += `<b class="block_txt nobr" style="background:${item.color}; color:#fff; margin: 0.1em 0.2em;">${item.label}</b>`;
- });
- });
- };
- return {
- add,
- remove,
- run,
- data,
- };
- })();
- // 获取主题数量
- const getTopicNum = async (uid) => {
- const api = `/thread.php?lite=js&authorid=${uid}`;
- const { __ROWS } = await new Promise((resolve) => {
- request(api)
- .then((res) => res.blob())
- .then((blob) => {
- const reader = new FileReader();
- reader.onload = () => {
- const text = reader.result;
- const result = JSON.parse(
- text.replace("window.script_muti_get_var_store=", "")
- );
- resolve(result.data);
- };
- reader.readAsText(blob, "GBK");
- })
- .catch(() => {
- resolve({});
- });
- });
- return __ROWS;
- };
- // 小号过滤和声望过滤、流量号过滤
- const getFilterModeByUserInfo = async (
- userInfo,
- reputation,
- subject,
- content
- ) => {
- const filterRegdateLimit = data.options.filterRegdateLimit || 0;
- const filterPostnumLimit = data.options.filterPostnumLimit || 0;
- const filterTopicRateLimit = data.options.filterTopicRateLimit || 100;
- const filterReputationLimit = data.options.filterReputationLimit || NaN;
- if (userInfo) {
- const { uid, username, regdate, postnum } = userInfo;
- if (
- filterRegdateLimit > 0 &&
- regdate * 1000 > new Date() - filterRegdateLimit
- ) {
- m.add({
- user: `<a href="${
- uid > 0 ? `/nuke.php?func=ucp&uid=${uid}` : `javascript:void(0)`
- }" class="b nobr">[${username ? "@" + username : "#" + uid}]</a>`,
- mode: "隐藏",
- subject,
- content,
- reason: `注册时间: ${new Date(regdate * 1000).toLocaleDateString()}`,
- });
- return true;
- }
- if (filterPostnumLimit > 0 && postnum < filterPostnumLimit) {
- m.add({
- user: `<a href="${
- uid > 0 ? `/nuke.php?func=ucp&uid=${uid}` : `javascript:void(0)`
- }" class="b nobr">[${username ? "@" + username : "#" + uid}]</a>`,
- mode: "隐藏",
- subject,
- content,
- reason: `发帖数量: ${postnum}`,
- });
- return true;
- }
- if (filterTopicRateLimit > 0 && filterTopicRateLimit < 100) {
- const topicNum = await getTopicNum(uid);
- const topicRate = (topicNum / postnum) * 100;
- if (topicRate > filterTopicRateLimit) {
- m.add({
- user: `<a href="${
- uid > 0 ? `/nuke.php?func=ucp&uid=${uid}` : `javascript:void(0)`
- }" class="b nobr">[${username ? "@" + username : "#" + uid}]</a>`,
- mode: "隐藏",
- subject,
- content,
- reason: `发帖比例: ${topicRate.toFixed(0)}%`,
- });
- return true;
- }
- }
- }
- if (Number.isNaN(filterReputationLimit) === false) {
- if (reputation < filterReputationLimit) {
- const { uid, username } = userInfo;
- m.add({
- user: `<a href="${
- uid > 0 ? `/nuke.php?func=ucp&uid=${uid}` : `javascript:void(0)`
- }" class="b nobr">[${username ? "@" + username : "#" + uid}]</a>`,
- mode: "隐藏",
- subject,
- content,
- reason: `声望: ${reputation}`,
- });
- return true;
- }
- }
- return false;
- };
- // 判断过滤方式
- const getFilterMode = async (uid, subject, content) => {
- let result = -1;
- const user = data.users[uid];
- const tags = user ? user.tags.map((tag) => data.tags[tag]) : [];
- const keywords = Object.values(data.keywords);
- const locations = Object.values(data.locations);
- if (uid > 0) {
- const userInfo = n.userInfo.users[uid];
- const reputation = (() => {
- const reputations = n.userInfo.reputations;
- if (reputations) {
- for (let fid in reputations) {
- return reputations[fid][uid] || 0;
- }
- }
- return NaN;
- })();
- if (
- await getFilterModeByUserInfo(userInfo, reputation, subject, content)
- ) {
- return FILTER_MODE.indexOf("隐藏");
- }
- } else if (uid < 0 && data.options.filterAnony) {
- m.add({
- user: `<a href="javascript:void(0)" class="b nobr">[匿名]</a>`,
- mode: "隐藏",
- subject,
- content,
- reason: `匿名`,
- });
- return FILTER_MODE.indexOf("隐藏");
- }
- if (user) {
- const filterMode = FILTER_MODE.indexOf(user.filterMode);
- if (filterMode > 0) {
- m.add({
- user: `<a href="/nuke.php?func=ucp&uid=${user.id}" class="b nobr">[${
- user.name ? "@" + user.name : "#" + user.id
- }]</a>`,
- mode: FILTER_MODE[filterMode],
- subject,
- content,
- reason: `用户模式: ${FILTER_MODE[filterMode]}`,
- });
- return filterMode;
- }
- result = filterMode;
- }
- if (tags.length) {
- const filterMode = (() => {
- if (tags.some((tag) => tag.filterMode !== "显示")) {
- return tags
- .filter((tag) => tag.filterMode !== "显示")
- .map((tag) => FILTER_MODE.indexOf(tag.filterMode) || 0)
- .sort((a, b) => b - a)[0];
- }
- return FILTER_MODE.indexOf("显示");
- })();
- if (filterMode > 0) {
- m.add({
- user: `<a href="/nuke.php?func=ucp&uid=${user.id}" class="b nobr">[${
- user.name ? "@" + user.name : "#" + user.id
- }]</a>`,
- mode: FILTER_MODE[filterMode],
- subject,
- content,
- reason: `标记模式: ${FILTER_MODE[filterMode]}`,
- });
- return filterMode;
- }
- result = filterMode;
- }
- if (keywords.length) {
- const filterMode = (() => {
- const sR = (() => {
- if (subject) {
- const r = keywords
- .filter((item) => item.keyword && item.filterMode !== "显示")
- .filter((item) => (item.filterLevel || 0) >= 0)
- .sort(
- (a, b) =>
- FILTER_MODE.indexOf(b.filterMode) -
- FILTER_MODE.indexOf(a.filterMode)
- )
- .find((item) => subject.search(item.keyword) >= 0);
- if (r) {
- if (uid > 0) {
- const { username } = n.userInfo.users[uid];
- m.add({
- user: `<a href="/nuke.php?func=ucp&uid=${uid}" class="b nobr">[@${username}]</a>`,
- mode: r.keyword,
- subject,
- content,
- reason: `关键词模式: ${r.keyword}`,
- });
- } else {
- m.add({
- user: `<a href="javascript:void(0)" class="b nobr">[匿名]</a>`,
- mode: r.keyword,
- subject,
- content,
- reason: `关键词模式: ${r.keyword}`,
- });
- }
- return FILTER_MODE.indexOf(r.filterMode);
- }
- }
- return -1;
- })();
- const cR = (() => {
- if (content) {
- const r = keywords
- .filter((item) => item.keyword && item.filterMode !== "显示")
- .filter((item) => (item.filterLevel || 0) >= 1)
- .sort(
- (a, b) =>
- FILTER_MODE.indexOf(b.filterMode) -
- FILTER_MODE.indexOf(a.filterMode)
- )
- .find((item) => content.search(item.keyword) >= 0);
- if (r) {
- if (uid > 0) {
- const { username } = n.userInfo.users[uid];
- m.add({
- user: `<a href="/nuke.php?func=ucp&uid=${uid}" class="b nobr">[@${username}]</a>`,
- mode: r.filterMode,
- subject,
- content,
- reason: `关键词模式: ${r.filterMode}`,
- });
- } else {
- m.add({
- user: `<a href="javascript:void(0)" class="b nobr">[匿名]</a>`,
- mode: r.filterMode,
- subject,
- content,
- reason: `关键词模式: ${r.filterMode}`,
- });
- }
- return FILTER_MODE.indexOf(r.filterMode);
- }
- }
- return -1;
- })();
- return Math.max(sR, cR, result);
- })();
- if (filterMode > 0) {
- return filterMode;
- }
- result = filterMode;
- }
- if (locations.length) {
- const { ipLoc } = await new Promise((resolve) => {
- request(`/nuke.php?lite=js&__lib=ucp&__act=get&uid=${uid}`)
- .then((res) => res.blob())
- .then((blob) => {
- const reader = new FileReader();
- reader.onload = () => {
- const text = reader.result;
- const result = JSON.parse(
- text.replace("window.script_muti_get_var_store=", "")
- );
- resolve(result.data[0]);
- };
- reader.readAsText(blob, "GBK");
- })
- .catch(() => {
- resolve({});
- });
- });
- if (ipLoc) {
- const filterMode = (() => {
- const r = locations
- .filter((item) => item.keyword && item.filterMode !== "显示")
- .sort(
- (a, b) =>
- FILTER_MODE.indexOf(b.filterMode) -
- FILTER_MODE.indexOf(a.filterMode)
- )
- .find((item) => ipLoc.search(item.keyword) >= 0);
- if (r) {
- const { username } = n.userInfo.users[uid];
- m.add({
- user: `<a href="/nuke.php?func=ucp&uid=${uid}" class="b nobr">[@${username}]</a>`,
- mode: r.filterMode,
- subject,
- content,
- reason: `属地模式: ${r.filterMode}`,
- });
- return FILTER_MODE.indexOf(r.filterMode);
- }
- return Math.max(r, result);
- })();
- if (filterMode > 0) {
- return filterMode;
- }
- result = filterMode;
- }
- }
- return result;
- };
- // 根据 TID 获取过滤方式
- const getFilterModeByTopic = async (tid) => {
- return await new Promise((resolve, reject) => {
- const api = `/read.php?tid=${tid}`;
- request(api)
- .then((res) => res.blob())
- .then((blob) => {
- const getLastIndex = (content, position) => {
- if (position >= 0) {
- let nextIndex = position + 1;
- while (nextIndex < content.length) {
- if (content[nextIndex] === "}") {
- return nextIndex;
- }
- if (content[nextIndex] === "{") {
- nextIndex = getLastIndex(content, nextIndex);
- if (nextIndex < 0) {
- break;
- }
- }
- nextIndex = nextIndex + 1;
- }
- }
- return -1;
- };
- const reader = new FileReader();
- reader.onload = async () => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(reader.result, "text/html");
- const html = doc.body.innerHTML;
- // 验证帖子正常
- const verify = doc.querySelector("#m_posts");
- if (verify) {
- // 取得顶楼 UID
- const uid = (() => {
- const ele = doc.querySelector("#postauthor0");
- if (ele) {
- const res = ele.getAttribute("href").match(/uid=(\S+)/);
- if (res) {
- return res[1];
- }
- }
- return 0;
- })();
- // 取得顶楼标题
- const subject = doc.querySelector("#postsubject0").innerHTML;
- // 取得顶楼内容
- const content = doc.querySelector("#postcontent0").innerHTML;
- if (uid && uid > 0) {
- // 取得用户信息
- const userInfo = (() => {
- // 起始JSON
- const str = `"${uid}":{`;
- // 起始下标
- const index = html.indexOf(str) + str.length;
- // 结尾下标
- const lastIndex = getLastIndex(html, index);
- if (lastIndex >= 0) {
- try {
- return JSON.parse(
- `{${html.substring(index, lastIndex)}}`
- );
- } catch {}
- }
- return null;
- })();
- // 取得用户声望
- const reputation = (() => {
- const reputations = (() => {
- // 起始JSON
- const str = `"__REPUTATIONS":{`;
- // 起始下标
- const index = html.indexOf(str) + str.length;
- // 结尾下标
- const lastIndex = getLastIndex(html, index);
- if (lastIndex >= 0) {
- return JSON.parse(
- `{${html.substring(index, lastIndex)}}`
- );
- }
- return null;
- })();
- if (reputations) {
- for (let fid in reputations) {
- return reputations[fid][uid] || 0;
- }
- }
- return NaN;
- })();
- if (
- await getFilterModeByUserInfo(
- userInfo,
- reputation,
- subject,
- content
- )
- ) {
- resolve(FILTER_MODE.indexOf("隐藏"));
- }
- }
- resolve(getFilterMode(uid, subject, content));
- } else {
- reject();
- }
- };
- reader.readAsText(blob, "GBK");
- })
- .catch(() => {
- reject();
- });
- }).catch(() => {
- return FILTER_MODE.indexOf("隐藏");
- });
- };
- // 处理引用
- const handleQuote = async (content) => {
- const quotes = content.querySelectorAll(".quote");
- await Promise.all(
- [...quotes].map(async (quote) => {
- const uid = (() => {
- const ele = quote.querySelector("a[href^='/nuke.php']");
- if (ele) {
- const res = ele.getAttribute("href").match(/uid=(\S+)/);
- if (res) {
- return res[1];
- }
- }
- return 0;
- })();
- const filterMode = await new Promise(async (resolve) => {
- const mode = await getFilterMode(uid, "", quote.innerText);
- if (mode === 0) {
- resolve(data.options.filterMode);
- }
- if (mode > 0) {
- resolve(FILTER_MODE[mode]);
- }
- resolve("");
- });
- if (filterMode === "标记") {
- quote.innerHTML = `
- <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
- <span class="crimson">Troll must die.</span>
- <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
- <div style="display: none;" name="troll_${uid}">
- ${quote.innerHTML}
- </div>
- </div>`;
- } else if (filterMode === "遮罩") {
- const source = document.createElement("DIV");
- source.innerHTML = quote.innerHTML;
- source.style.display = "none";
- const caption = document.createElement("CAPTION");
- caption.className = "filter-mask filter-mask-block";
- caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
- caption.onclick = () => {
- quote.removeChild(caption);
- source.style.display = "";
- };
- quote.innerHTML = "";
- quote.appendChild(source);
- quote.appendChild(caption);
- } else if (filterMode === "隐藏") {
- quote.innerHTML = "";
- }
- })
- );
- };
- // 过滤
- const reFilter = (() => {
- let hasNext = false;
- let isRunning = false;
- const func = async () => {
- const tPage = location.pathname === "/thread.php";
- const pPage = location.pathname === "/read.php";
- if (tPage) {
- const params = new URLSearchParams(location.search);
- if (params.has("favor")) {
- return;
- }
- if (params.has("authorid")) {
- return;
- }
- }
- if (tPage) {
- const tData = n.topicArg.data;
- await Promise.all(
- Object.values(tData).map(async (item) => {
- if (item.containerC) return;
- const tid = item[8];
- const filterMode = await new Promise(async (resolve) => {
- const mode = await getFilterModeByTopic(tid);
- if (mode === 0) {
- resolve(data.options.filterMode);
- }
- if (mode > 0) {
- resolve(FILTER_MODE[mode]);
- }
- resolve("");
- });
- item.contentC = item[1];
- item.contentB = item.contentB || item.contentC.innerHTML;
- item.containerC =
- item.containerC || item.contentC.parentNode.parentNode;
- item.containerC.style = "";
- item.contentC.style = "";
- item[1].className = item[1].className.replace(" filter-mask", "");
- item[2].className = item[2].className.replace(" filter-mask", "");
- if (filterMode === "标记") {
- item.contentC.style = "text-decoration: line-through;";
- } else if (filterMode === "遮罩") {
- item[1].className += " filter-mask";
- item[2].className += " filter-mask";
- } else if (filterMode === "隐藏") {
- item.containerC.style = "display: none;";
- }
- })
- );
- } else if (pPage) {
- const pData = n.postArg.data;
- await Promise.all(
- Object.values(pData).map(async (item) => {
- const uid = ~~item.pAid;
- if (uid === self) return;
- if (item.containerC) return;
- if (typeof item.i === "number") {
- item.actionC =
- item.actionC ||
- (() => {
- const container =
- item.uInfoC
- .closest("tr")
- .querySelector(".posterInfoLine") ||
- item.uInfoC.querySelector("div");
- const ele = container.querySelector('[name="uid"]');
- if (ele) {
- ele.innerHTML = `屏蔽`;
- ele.onclick = null;
- return ele;
- }
- const anchor = container.querySelector(".author ~ br");
- if (anchor) {
- const btn = document.createElement("A");
- btn.name = "uid";
- btn.href = "javascript:void(0)";
- btn.className =
- "small_colored_text_btn stxt block_txt_c0 vertmod";
- btn.innerHTML = `屏蔽`;
- anchor.parentNode.insertBefore(btn, anchor);
- return btn;
- }
- })();
- item.tagC =
- item.tagC ||
- (() => {
- const container =
- item.uInfoC
- .closest("tr")
- .querySelector(".posterInfoLine") || item.uInfoC;
- const tc = document.createElement("div");
- tc.className = "filter-tags";
- container.appendChild(tc);
- return tc;
- })();
- }
- item.pName =
- item.pName ||
- (() => {
- const container =
- item.uInfoC.closest("tr").querySelector(".posterInfoLine") ||
- item.uInfoC;
- return container.querySelector(".author").innerText;
- })();
- item.reFilter =
- item.reFilter ||
- (async () => {
- const filterMode = await new Promise(async (resolve) => {
- const mode = await getFilterMode(
- uid,
- item.subjectC.innerText,
- item.contentC.innerText
- );
- if (mode === 0) {
- resolve(data.options.filterMode);
- }
- if (mode > 0) {
- resolve(FILTER_MODE[mode]);
- }
- resolve("");
- });
- 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;
- if (item.i >= 0) {
- while (temp.nodeName !== "TBODY") {
- temp = temp.parentNode;
- }
- } else {
- while (temp.nodeName !== "DIV") {
- temp = temp.parentNode;
- }
- }
- return temp;
- })();
- item.avatarC.style.display = "";
- item.containerC.style.display = "";
- item.contentC.innerHTML = item.contentB;
- if (item.actionC) {
- item.actionC.style = "background: #aaa;";
- if (uid < 0) {
- item.actionC.style.display = "none";
- }
- }
- if (filterMode === "标记") {
- item.avatarC.style.display = "none";
- item.contentC.innerHTML = `
- <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
- <span class="crimson">Troll must die.</span>
- <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
- <div style="display: none;" name="troll_${uid}">
- ${item.contentB}
- </div>
- </div>`;
- if (item.actionC && data.users[uid]) {
- item.actionC.style = "background: #cb4042;";
- }
- } else if (filterMode === "遮罩") {
- const caption = document.createElement("CAPTION");
- if (item.i >= 0) {
- caption.className = "filter-mask filter-mask-block";
- } else {
- caption.className = "filter-mask filter-mask-block left";
- caption.style = "width: 47%;";
- }
- caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
- caption.onclick = () => {
- item.containerC.parentNode.removeChild(caption);
- item.containerC.style.display = "";
- };
- item.containerC.parentNode.insertBefore(
- caption,
- item.containerC
- );
- item.containerC.style.display = "none";
- if (item.actionC && data.users[uid]) {
- item.actionC.style = "background: #cb4042;";
- }
- } else if (filterMode === "隐藏") {
- item.containerC.style.display = "none";
- } else {
- await handleQuote(item.contentC);
- }
- if (item.tagC) {
- const tags = data.users[uid]
- ? data.users[uid].tags.map((tag) => data.tags[tag]) || []
- : [];
- item.tagC.style.display = tags.length ? "" : "none";
- item.tagC.innerHTML = tags
- .map(
- (tag) =>
- `<b class="block_txt nobr" style="background:${tag.color}; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>`
- )
- .join("");
- witchHunter.run(uid, item.tagC);
- }
- });
- if (item.actionC) {
- item.actionC.onclick =
- item.actionC.onclick ||
- ((e) => {
- if (item.pAid < 0) return;
- const user = data.users[item.pAid];
- if (e.ctrlKey === false) {
- editUser(item.pAid, item.pName, item.reFilter);
- } else {
- if (user) {
- delete data.users[user.id];
- } else {
- addUser(item.pAid, item.pName);
- }
- saveData();
- item.reFilter();
- }
- });
- }
- await item.reFilter();
- })
- );
- }
- };
- const execute = () =>
- func().finally(() => {
- if (hasNext) {
- hasNext = false;
- execute();
- } else {
- isRunning = false;
- }
- });
- return async () => {
- if (isRunning) {
- hasNext = true;
- } else {
- isRunning = true;
- await execute();
- }
- };
- })();
- // STYLE
- GM_addStyle(`
- .filter-table-wrapper {
- max-height: 80vh;
- overflow-y: auto;
- }
- .filter-table {
- margin: 0;
- }
- .filter-table th,
- .filter-table td {
- position: relative;
- white-space: nowrap;
- }
- .filter-table th {
- position: sticky;
- top: 2px;
- z-index: 1;
- }
- .filter-table input:not([type]), .filter-table input[type="text"] {
- margin: 0;
- box-sizing: border-box;
- height: 100%;
- width: 100%;
- }
- .filter-input-wrapper {
- position: absolute;
- top: 6px;
- right: 6px;
- bottom: 6px;
- left: 6px;
- }
- .filter-text-ellipsis {
- display: flex;
- }
- .filter-text-ellipsis > * {
- flex: 1;
- width: 1px;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .filter-button-group {
- margin: -.1em -.2em;
- }
- .filter-tags {
- margin: 2px -0.2em 0;
- text-align: left;
- }
- .filter-mask {
- margin: 1px;
- color: #81C7D4;
- background: #81C7D4;
- }
- .filter-mask-block {
- display: block;
- border: 1px solid #66BAB7;
- text-align: center !important;
- }
- .filter-input-wrapper {
- position: absolute;
- top: 6px;
- right: 6px;
- bottom: 6px;
- left: 6px;
- }
- `);
- // MENU
- const m = (() => {
- const list = [];
- const container = document.createElement("DIV");
- container.className = `td`;
- container.innerHTML = `<a class="mmdefault" href="javascript: void(0);" style="white-space: nowrap;">屏蔽</a>`;
- const content = container.querySelector("A");
- const create = (onclick) => {
- const anchor = document.querySelector("#mainmenu .td:last-child");
- anchor.before(container);
- content.onclick = onclick;
- };
- const update = () => {
- const count = list.length;
- if (count) {
- content.innerHTML = `屏蔽 <span class="small_colored_text_btn stxt block_txt_c0 vertmod">${count}</span>`;
- } else {
- content.innerHTML = `屏蔽`;
- }
- };
- const clear = () => {
- list.splice(0, list.length);
- update();
- };
- const add = ({ user, mode, subject, content, reason }) => {
- if (list.find((item) => item.content === content)) return;
- list.unshift({ user, mode, subject, content, reason });
- listModule.refresh();
- update();
- };
- return {
- create,
- clear,
- list,
- add,
- };
- })();
- // UI
- const u = (() => {
- const modules = {};
- const tabContainer = (() => {
- const c = document.createElement("div");
- c.className = "w100";
- c.innerHTML = `
- <div class="right_" style="margin-bottom: 5px;">
- <table class="stdbtn" cellspacing="0">
- <tbody>
- <tr></tr>
- </tbody>
- </table>
- </div>
- <div class="clear"></div>
- `;
- return c;
- })();
- const tabPanelContainer = (() => {
- const c = document.createElement("div");
- c.style = "width: 80vw;";
- 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 = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`;
- 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 listModule = (() => {
- const content = (() => {
- const c = document.createElement("div");
- c.style = "display: none";
- c.innerHTML = `
- <div class="filter-table-wrapper">
- <table class="filter-table forumbox">
- <thead>
- <tr class="block_txt_c0">
- <th class="c1" width="1">用户</th>
- <th class="c2" width="1">过滤方式</th>
- <th class="c3">内容</th>
- <th class="c4" width="1">原因</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- `;
- return c;
- })();
- const refresh = (() => {
- const container = content.getElementsByTagName("tbody")[0];
- const func = () => {
- container.innerHTML = "";
- Object.values(m.list).forEach((item) => {
- const tc = document.createElement("tr");
- tc.className = `row${
- (container.querySelectorAll("TR").length % 2) + 1
- }`;
- tc.refresh = () => {
- const { user, mode, subject, content, reason } = item;
- tc.innerHTML = `
- <td class="c1">${user}</td>
- <td class="c2">${mode}</td>
- <td class="c3">
- <div class="filter-text-ellipsis">
- <span title="${content}">${subject || content}</span>
- </div>
- </td>
- <td class="c4">${reason}</td>
- `;
- };
- tc.refresh();
- container.appendChild(tc);
- });
- };
- return func;
- })();
- return {
- name: "列表",
- content,
- refresh,
- };
- })();
- // 用户
- const userModule = (() => {
- const content = (() => {
- const c = document.createElement("div");
- c.style = "display: none";
- c.innerHTML = `
- <div class="filter-table-wrapper">
- <table class="filter-table forumbox">
- <thead>
- <tr class="block_txt_c0">
- <th class="c1" width="1">昵称</th>
- <th class="c2">标记</th>
- <th class="c3" width="1">过滤方式</th>
- <th class="c4" width="1">操作</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- `;
- 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.className = `row${
- (container.querySelectorAll("TR").length % 2) + 1
- }`;
- tc.refresh = () => {
- if (data.users[item.id]) {
- tc.innerHTML = `
- <td class="c1">
- <a href="/nuke.php?func=ucp&uid=${
- item.id
- }" class="b nobr">[${
- item.name ? "@" + item.name : "#" + item.id
- }]</a>
- </td>
- <td class="c2">
- ${item.tags
- .map((tag) => {
- if (data.tags[tag]) {
- return `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`;
- }
- })
- .join("")}
- </td>
- <td class="c3">
- <div class="filter-table-button-group">
- <button>${item.filterMode || FILTER_MODE[0]}</button>
- </div>
- </td>
- <td class="c4">
- <div class="filter-table-button-group">
- <button>编辑</button>
- <button>删除</button>
- </div>
- </td>
- `;
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = () => {
- data.users[item.id].filterMode = switchFilterMode(
- data.users[item.id].filterMode || FILTER_MODE[0]
- );
- actions[0].innerHTML = data.users[item.id].filterMode;
- saveData();
- reFilter();
- };
- actions[1].onclick = () => {
- editUser(item.id, item.name, tc.refresh);
- };
- 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 = `
- <div class="filter-table-wrapper">
- <table class="filter-table forumbox">
- <thead>
- <tr class="block_txt_c0">
- <th class="c1" width="1">标记</th>
- <th class="c2">列表</th>
- <th class="c3" width="1">过滤方式</th>
- <th class="c4" width="1">操作</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- `;
- 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.className = `row${
- (container.querySelectorAll("TR").length % 2) + 1
- }`;
- tc.innerHTML = `
- <td class="c1">
- <b class="block_txt nobr" style="background:${
- item.color
- }; color:#fff; margin: 0.1em 0.2em;">${item.name}</b>
- </td>
- <td class="c2">
- <button>${
- Object.values(data.users).filter((user) =>
- user.tags.find((tag) => tag === item.id)
- ).length
- }
- </button>
- <div style="white-space: normal; display: none;">
- ${Object.values(data.users)
- .filter((user) =>
- user.tags.find((tag) => tag === item.id)
- )
- .map(
- (user) =>
- `<a href="/nuke.php?func=ucp&uid=${
- user.id
- }" class="b nobr">[${
- user.name ? "@" + user.name : "#" + user.id
- }]</a>`
- )
- .join("")}
- </div>
- </td>
- <td class="c3">
- <div class="filter-table-button-group">
- <button>${item.filterMode || FILTER_MODE[0]}</button>
- </div>
- </td>
- <td class="c4">
- <div class="filter-table-button-group">
- <button>删除</button>
- </div>
- </td>
- `;
- 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].filterMode = switchFilterMode(
- data.tags[item.id].filterMode || FILTER_MODE[0]
- );
- actions[1].innerHTML = data.tags[item.id].filterMode;
- 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 keywordModule = (() => {
- const content = (() => {
- const c = document.createElement("div");
- c.style = "display: none";
- c.innerHTML = `
- <div class="filter-table-wrapper">
- <table class="filter-table forumbox">
- <thead>
- <tr class="block_txt_c0">
- <th class="c1">列表</th>
- <th class="c2" width="1">过滤方式</th>
- <th class="c3" width="1">包括内容</th>
- <th class="c4" width="1">操作</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用"|"隔开,"ABC|DEF"即为屏蔽带有ABC或者DEF的内容。</div>
- `;
- return c;
- })();
- const refresh = (() => {
- const container = content.getElementsByTagName("tbody")[0];
- const func = () => {
- container.innerHTML = "";
- Object.values(data.keywords).forEach((item) => {
- const tc = document.createElement("tr");
- tc.className = `row${
- (container.querySelectorAll("TR").length % 2) + 1
- }`;
- tc.innerHTML = `
- <td class="c1">
- <div class="filter-input-wrapper">
- <input value="${item.keyword || ""}" />
- </div>
- </td>
- <td class="c2">
- <div class="filter-table-button-group">
- <button>${item.filterMode || FILTER_MODE[0]}</button>
- </div>
- </td>
- <td class="c3">
- <div style="text-align: center;">
- <input type="checkbox" ${
- item.filterLevel ? `checked="checked"` : ""
- } />
- </div>
- </td>
- <td class="c4">
- <div class="filter-table-button-group">
- <button>保存</button>
- <button>删除</button>
- </div>
- </td>
- `;
- const inputElement = tc.querySelector("INPUT");
- const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = () => {
- actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
- };
- actions[1].onclick = () => {
- if (inputElement.value) {
- data.keywords[item.id] = {
- id: item.id,
- keyword: inputElement.value,
- filterMode: actions[0].innerHTML,
- filterLevel: levelElement.checked ? 1 : 0,
- };
- saveData();
- refresh();
- }
- };
- actions[2].onclick = () => {
- if (confirm("是否确认?")) {
- delete data.keywords[item.id];
- saveData();
- refresh();
- }
- };
- container.appendChild(tc);
- });
- {
- const tc = document.createElement("tr");
- tc.className = `row${
- (container.querySelectorAll("TR").length % 2) + 1
- }`;
- tc.innerHTML = `
- <td class="c1">
- <div class="filter-input-wrapper">
- <input value="" />
- </div>
- </td>
- <td class="c2">
- <div class="filter-table-button-group">
- <button>${FILTER_MODE[0]}</button>
- </div>
- </td>
- <td class="c3">
- <div style="text-align: center;">
- <input type="checkbox" />
- </div>
- </td>
- <td class="c4">
- <div class="filter-table-button-group">
- <button>添加</button>
- </div>
- </td>
- `;
- const inputElement = tc.querySelector("INPUT");
- const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = () => {
- actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
- };
- actions[1].onclick = () => {
- if (inputElement.value) {
- addKeyword(
- inputElement.value,
- actions[0].innerHTML,
- levelElement.checked ? 1 : 0
- );
- saveData();
- refresh();
- }
- };
- container.appendChild(tc);
- }
- };
- return func;
- })();
- return {
- name: "关键字",
- content,
- refresh,
- };
- })();
- // 属地
- const locationModule = (() => {
- const content = (() => {
- const c = document.createElement("div");
- c.style = "display: none";
- c.innerHTML = `
- <div class="filter-table-wrapper">
- <table class="filter-table forumbox">
- <thead>
- <tr class="block_txt_c0">
- <th class="c1">列表</th>
- <th class="c2" width="1">过滤方式</th>
- <th class="c3" width="1">操作</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用"|"隔开,"ABC|DEF"即为屏蔽带有ABC或者DEF的内容。<br/>属地过滤功能需要占用额外的资源,请谨慎开启</div>
- `;
- return c;
- })();
- const refresh = (() => {
- const container = content.getElementsByTagName("tbody")[0];
- const func = () => {
- container.innerHTML = "";
- Object.values(data.locations).forEach((item) => {
- const tc = document.createElement("tr");
- tc.className = `row${
- (container.querySelectorAll("TR").length % 2) + 1
- }`;
- tc.innerHTML = `
- <td class="c1">
- <div class="filter-input-wrapper">
- <input value="${item.keyword || ""}" />
- </div>
- </td>
- <td class="c2">
- <div class="filter-table-button-group">
- <button>${item.filterMode || FILTER_MODE[0]}</button>
- </div>
- </td>
- <td class="c3">
- <div class="filter-table-button-group">
- <button>保存</button>
- <button>删除</button>
- </div>
- </td>
- `;
- const inputElement = tc.querySelector("INPUT");
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = () => {
- actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
- };
- actions[1].onclick = () => {
- if (inputElement.value) {
- data.locations[item.id] = {
- id: item.id,
- keyword: inputElement.value,
- filterMode: actions[0].innerHTML,
- };
- saveData();
- refresh();
- }
- };
- actions[2].onclick = () => {
- if (confirm("是否确认?")) {
- delete data.locations[item.id];
- saveData();
- refresh();
- }
- };
- container.appendChild(tc);
- });
- {
- const tc = document.createElement("tr");
- tc.className = `row${
- (container.querySelectorAll("TR").length % 2) + 1
- }`;
- tc.innerHTML = `
- <td class="c1">
- <div class="filter-input-wrapper">
- <input value="" />
- </div>
- </td>
- <td class="c2">
- <div class="filter-table-button-group">
- <button>${FILTER_MODE[0]}</button>
- </div>
- </td>
- <td class="c3">
- <div class="filter-table-button-group">
- <button>添加</button>
- </div>
- </td>
- `;
- const inputElement = tc.querySelector("INPUT");
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = () => {
- actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
- };
- actions[1].onclick = () => {
- if (inputElement.value) {
- addLocation(inputElement.value, actions[0].innerHTML);
- saveData();
- refresh();
- }
- };
- container.appendChild(tc);
- }
- };
- return func;
- })();
- return {
- name: "属地",
- content,
- refresh,
- };
- })();
- // 猎巫
- const witchHuntModule = (() => {
- const content = (() => {
- const c = document.createElement("div");
- c.style = "display: none";
- c.innerHTML = `
- <div class="filter-table-wrapper">
- <table class="filter-table forumbox">
- <thead>
- <tr class="block_txt_c0">
- <th class="c1">版面</th>
- <th class="c2">标签</th>
- <th class="c3" width="1">操作</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- <div class="silver" style="margin-top: 10px;">猎巫模块需要占用额外的资源,请谨慎开启<br/>该功能为实验性功能,仅判断用户是否曾经在某个版面发言<br/>未来可能会加入发言的筛选或是屏蔽功能,也可能移除此功能</div>
- `;
- return c;
- })();
- const refresh = (() => {
- const container = content.getElementsByTagName("tbody")[0];
- const func = () => {
- container.innerHTML = "";
- Object.values(witchHunter.data).forEach((item, index) => {
- const tc = document.createElement("tr");
- tc.className = `row${
- (container.querySelectorAll("TR").length % 2) + 1
- }`;
- tc.innerHTML = `
- <td class="c1">
- <div class="filter-input-wrapper">
- <a href="/thread.php?fid=${item.fid}" class="b nobr">[${item.name}]</a>
- </div>
- </td>
- <td class="c2">
- <b class="block_txt nobr" style="background:${item.color}; color:#fff; margin: 0.1em 0.2em;">${item.label}</b>
- </td>
- <td class="c3">
- <div class="filter-table-button-group">
- <button>删除</button>
- </div>
- </td>
- `;
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = () => {
- if (confirm("是否确认?")) {
- witchHunter.remove(item.id);
- refresh();
- }
- };
- container.appendChild(tc);
- });
- {
- const tc = document.createElement("tr");
- tc.className = `row${
- (container.querySelectorAll("TR").length % 2) + 1
- }`;
- tc.innerHTML = `
- <td class="c1">
- <div class="filter-input-wrapper">
- <input value="" placeholder="版面ID" />
- </div>
- </td>
- <td class="c2">
- <div class="filter-input-wrapper">
- <input value="" />
- </div>
- </td>
- <td class="c3">
- <div class="filter-table-button-group">
- <button>添加</button>
- </div>
- </td>
- `;
- const inputElement = tc.getElementsByTagName("INPUT");
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = async () => {
- const fid = parseInt(inputElement[0].value, 10);
- const tag = inputElement[1].value.trim();
- if (isNaN(fid) || tag.length === 0) {
- return;
- }
- await witchHunter.add(fid, tag);
- refresh();
- };
- 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 += `
- <div>默认过滤方式</div>
- <div></div>
- <div class="silver" style="margin-top: 10px;">${FILTER_TIPS}</div>
- `;
- ["标记", "遮罩", "隐藏"].forEach((item, index) => {
- const ele = document.createElement("SPAN");
- ele.innerHTML += `
- <input id="s-fm-${index}" type="radio" name="filterType" ${
- data.options.filterMode === item && "checked"
- }>
- <label for="s-fm-${index}" style="cursor: pointer;">${item}</label>
- `;
- const inp = ele.querySelector("input");
- inp.onchange = () => {
- if (inp.checked) {
- data.options.filterMode = item;
- saveData();
- reFilter();
- }
- };
- tc.querySelectorAll("div")[1].append(ele);
- });
- container.appendChild(tc);
- }
- // 小号过滤(时间)
- {
- const tc = document.createElement("div");
- tc.innerHTML += `
- <br/>
- <div>
- 隐藏注册时间小于<input value="${
- (data.options.filterRegdateLimit || 0) / 86400000
- }" maxLength="4" style="width: 48px;" />天的用户
- <button>确认</button>
- </div>
- `;
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = () => {
- const v = actions[0].previousElementSibling.value;
- const n = Number(v) || 0;
- data.options.filterRegdateLimit = n < 0 ? 0 : n * 86400000;
- saveData();
- reFilter();
- };
- container.appendChild(tc);
- }
- // 小号过滤(发帖数)
- {
- const tc = document.createElement("div");
- tc.innerHTML += `
- <br/>
- <div>
- 隐藏发帖数量小于<input value="${
- data.options.filterPostnumLimit || 0
- }" maxLength="5" style="width: 48px;" />贴的用户
- <button>确认</button>
- </div>
- `;
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = () => {
- const v = actions[0].previousElementSibling.value;
- const n = Number(v) || 0;
- data.options.filterPostnumLimit = n < 0 ? 0 : n;
- saveData();
- reFilter();
- };
- container.appendChild(tc);
- }
- // 流量号过滤(主题比例)
- {
- const tc = document.createElement("div");
- tc.innerHTML += `
- <br/>
- <div>
- 隐藏发帖比例大于<input value="${
- data.options.filterTopicRateLimit || 100
- }" maxLength="3" style="width: 48px;" />%的用户
- <button>确认</button>
- </div>
- `;
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = () => {
- const v = actions[0].previousElementSibling.value;
- const n = Number(v) || 100;
- if (n <= 0 || n > 100) {
- return;
- }
- data.options.filterTopicRateLimit = n;
- saveData();
- reFilter();
- };
- container.appendChild(tc);
- }
- // 声望过滤
- {
- const tc = document.createElement("div");
- tc.innerHTML += `
- <br/>
- <div>
- 隐藏版面声望低于<input value="${
- data.options.filterReputationLimit || ""
- }" maxLength="5" style="width: 48px;" />点的用户
- <button>确认</button>
- </div>
- `;
- const actions = tc.getElementsByTagName("button");
- actions[0].onclick = () => {
- const v = actions[0].previousElementSibling.value;
- const n = Number(v);
- data.options.filterReputationLimit = n;
- saveData();
- reFilter();
- };
- container.appendChild(tc);
- }
- // 匿名过滤
- {
- const tc = document.createElement("div");
- tc.innerHTML += `
- <br/>
- <div>
- <label>
- 隐藏匿名的用户
- <input type="checkbox" ${
- data.options.filterAnony ? `checked="checked"` : ""
- } />
- </label>
- </div>
- `;
- const checkbox = tc.querySelector("input");
- checkbox.onchange = () => {
- const v = checkbox.checked;
- data.options.filterAnony = v;
- saveData();
- reFilter();
- };
- container.appendChild(tc);
- }
- // 删除没有标记的用户
- {
- const tc = document.createElement("div");
- tc.innerHTML += `
- <br/>
- <div>
- <button>删除没有标记的用户</button>
- </div>
- `;
- 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 += `
- <br/>
- <div>
- <button>删除没有用户的标记</button>
- </div>
- `;
- 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);
- }
- // 删除非激活中的用户
- {
- const tc = document.createElement("div");
- tc.innerHTML += `
- <br/>
- <div>
- <button>删除非激活中的用户</button>
- <div style="white-space: normal;"></div>
- </div>
- `;
- const action = tc.querySelector("button");
- const list = action.nextElementSibling;
- action.onclick = () => {
- if (confirm("是否确认?")) {
- const waitingQueue = Object.values(data.users).map(
- (item) => () =>
- new Promise((resolve) => {
- fetch(
- `/nuke.php?lite=js&__lib=ucp&__act=get&uid=${item.id}`
- )
- .then((res) => res.blob())
- .then((blob) => {
- const reader = new FileReader();
- reader.onload = () => {
- const text = reader.result;
- const result = JSON.parse(
- text.replace(
- "window.script_muti_get_var_store=",
- ""
- )
- );
- if (!result.error) {
- const { bit } = result.data[0];
- const activeInfo = n.activeInfo(0, 0, bit);
- const activeType = activeInfo[1];
- if (!["ACTIVED", "LINKED"].includes(activeType)) {
- list.innerHTML += `<a href="/nuke.php?func=ucp&uid=${
- item.id
- }" class="b nobr">[${
- item.name ? "@" + item.name : "#" + item.id
- }]</a>`;
- delete data.users[item.id];
- }
- }
- resolve();
- };
- reader.readAsText(blob, "GBK");
- })
- .catch(() => {
- resolve();
- });
- })
- );
- const queueLength = waitingQueue.length;
- const execute = () => {
- if (waitingQueue.length) {
- const next = waitingQueue.shift();
- action.innerHTML = `删除非激活中的用户 (${
- queueLength - waitingQueue.length
- }/${queueLength})`;
- action.disabled = true;
- next().finally(execute);
- } else {
- action.disabled = false;
- saveData();
- reFilter();
- }
- };
- execute();
- }
- };
- container.appendChild(tc);
- }
- };
- return func;
- })();
- return {
- name: "通用设置",
- content,
- refresh,
- };
- })();
- u.addModule(listModule).toggle();
- u.addModule(userModule);
- u.addModule(tagModule);
- u.addModule(keywordModule);
- u.addModule(locationModule);
- u.addModule(witchHuntModule);
- u.addModule(commonModule);
- // 增加菜单项
- (() => {
- let window;
- m.create(() => {
- if (window === undefined) {
- window = n.createCommmonWindow();
- }
- window._.addContent(null);
- window._.addTitle(`屏蔽`);
- window._.addContent(u.content);
- window._.show();
- });
- })();
- // 执行过滤
- (() => {
- const hookFunction = (object, functionName, callback) => {
- ((originalFunction) => {
- object[functionName] = function () {
- const returnValue = originalFunction.apply(this, arguments);
- callback.apply(this, [returnValue, originalFunction, arguments]);
- return returnValue;
- };
- })(object[functionName]);
- };
- const initialized = {
- topicArg: false,
- postArg: false,
- };
- hookFunction(n, "eval", () => {
- if (Object.values(initialized).findIndex((item) => item === false) < 0) {
- return;
- }
- if (n.topicArg && initialized.topicArg === false) {
- hookFunction(n.topicArg, "add", reFilter);
- initialized.topicArg = true;
- }
- if (n.postArg && initialized.postArg === false) {
- hookFunction(n.postArg, "proc", reFilter);
- initialized.postArg = true;
- }
- });
- reFilter();
- })();
- })(commonui, __CURRENT_UID);