NGA Filter

troll must die

当前为 2022-06-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name NGA Filter
  3. // @namespace https://greasyfork.org/users/263018
  4. // @version 1.7.0
  5. // @author snyssss
  6. // @description troll must die
  7. // @license MIT
  8.  
  9. // @match *://bbs.nga.cn/*
  10. // @match *://ngabbs.com/*
  11. // @match *://nga.178.com/*
  12.  
  13. // @grant GM_addStyle
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16.  
  17. // @noframes
  18. // ==/UserScript==
  19.  
  20. ((n, self) => {
  21. if (n === undefined) return;
  22.  
  23. const key = "NGAFilter";
  24.  
  25. // 过滤提示
  26. const FILTER_TIPS =
  27. "过滤顺序:用户 &gt; 标记 &gt; 关键字<br/>过滤级别:隐藏 &gt; 遮罩 &gt; 标记 &gt; 继承 &gt; 显示<br/>多个标记或者关键字按最高级别过滤";
  28.  
  29. // 过滤方式
  30. const FILTER_MODE = ["继承", "标记", "遮罩", "隐藏", "显示"];
  31.  
  32. // 切换过滤方式
  33. const switchFilterMode = (value) => {
  34. const next = FILTER_MODE.indexOf(value) + 1;
  35.  
  36. if (next >= FILTER_MODE.length) {
  37. return FILTER_MODE[0];
  38. }
  39.  
  40. return FILTER_MODE[next];
  41. };
  42.  
  43. // 数据
  44. const data = (() => {
  45. const d = {
  46. tags: {},
  47. users: {},
  48. keywords: {},
  49. options: {
  50. filterRegdateLimit: 0,
  51. filterPostnumLimit: 0,
  52. filterReputationLimit: NaN,
  53. filterMode: "隐藏",
  54. },
  55. };
  56.  
  57. const v = GM_getValue(key);
  58.  
  59. if (typeof v !== "object") {
  60. return d;
  61. }
  62.  
  63. return Object.assign(d, v);
  64. })();
  65.  
  66. // 保存数据
  67. const saveData = () => {
  68. GM_setValue(key, data);
  69. };
  70.  
  71. // 增加标记
  72. const addTag = (name) => {
  73. const tag = Object.values(data.tags).find((item) => item.name === name);
  74.  
  75. if (tag) return tag.id;
  76.  
  77. const id =
  78. Math.max(...Object.values(data.tags).map((item) => item.id), 0) + 1;
  79.  
  80. const hash = (() => {
  81. let h = 5381;
  82. for (var i = 0; i < name.length; i++) {
  83. h = ((h << 5) + h + name.charCodeAt(i)) & 0xffffffff;
  84. }
  85. return h;
  86. })();
  87.  
  88. const hex = Math.abs(hash).toString(16) + "000000";
  89.  
  90. const hsv = [
  91. `0x${hex.substring(2, 4)}` / 255,
  92. `0x${hex.substring(2, 4)}` / 255 / 2 + 0.25,
  93. `0x${hex.substring(4, 6)}` / 255 / 2 + 0.25,
  94. ];
  95.  
  96. const rgb = n.hsvToRgb(hsv[0], hsv[1], hsv[2]);
  97.  
  98. const color = ["#", ...rgb].reduce((a, b) => {
  99. return a + ("0" + b.toString(16)).slice(-2);
  100. });
  101.  
  102. data.tags[id] = {
  103. id,
  104. name,
  105. color,
  106. filterMode: FILTER_MODE[0],
  107. };
  108.  
  109. saveData();
  110.  
  111. return id;
  112. };
  113.  
  114. // 增加用户
  115. const addUser = (id, name = null, tags = [], filterMode = FILTER_MODE[0]) => {
  116. if (data.users[id]) return data.users[id];
  117.  
  118. data.users[id] = {
  119. id,
  120. name,
  121. tags,
  122. filterMode,
  123. };
  124.  
  125. saveData();
  126.  
  127. return data.users[id];
  128. };
  129.  
  130. // 增加关键字
  131. const addKeyword = (
  132. keyword,
  133. filterMode = FILTER_MODE[0],
  134. filterLevel = 0
  135. ) => {
  136. const id =
  137. Math.max(...Object.values(data.keywords).map((item) => item.id), 0) + 1;
  138.  
  139. data.keywords[id] = {
  140. id,
  141. keyword,
  142. filterMode,
  143. filterLevel,
  144. };
  145.  
  146. saveData();
  147.  
  148. return id;
  149. };
  150.  
  151. // 旧版本数据迁移
  152. {
  153. const dataKey = "troll_data";
  154. const modeKey = "troll_mode";
  155. const keywordKey = "troll_keyword";
  156.  
  157. if (localStorage.getItem(dataKey)) {
  158. let trollMap = (function () {
  159. try {
  160. return JSON.parse(localStorage.getItem(dataKey)) || {};
  161. } catch (e) {}
  162.  
  163. return {};
  164. })();
  165.  
  166. let filterMode = ~~localStorage.getItem(modeKey);
  167.  
  168. let filterKeyword = localStorage.getItem(keywordKey) || "";
  169.  
  170. // 整理标签
  171. [...new Set(Object.values(trollMap).flat())].forEach((item) =>
  172. addTag(item)
  173. );
  174.  
  175. // 整理用户
  176. Object.keys(trollMap).forEach((item) => {
  177. addUser(
  178. item,
  179. null,
  180. (typeof trollMap[item] === "object" ? trollMap[item] : []).map(
  181. (tag) => addTag(tag)
  182. )
  183. );
  184. });
  185.  
  186. data.options.filterMode = filterMode ? "隐藏" : "标记";
  187. data.options.keyword = filterKeyword;
  188.  
  189. localStorage.removeItem(dataKey);
  190. localStorage.removeItem(modeKey);
  191. localStorage.removeItem(keywordKey);
  192.  
  193. saveData();
  194. }
  195.  
  196. // v1.1.0 -> v1.1.1
  197. {
  198. Object.values(data.users).forEach(({ id, name, tags, enabled }) => {
  199. if (enabled !== undefined) {
  200. data.users[id] = {
  201. id,
  202. name,
  203. tags,
  204. filterMode: enabled ? "继承" : "显示",
  205. };
  206. }
  207. });
  208.  
  209. Object.values(data.tags).forEach(({ id, name, color, enabled }) => {
  210. if (enabled !== undefined) {
  211. data.tags[id] = {
  212. id,
  213. name,
  214. color,
  215. filterMode: enabled ? "继承" : "显示",
  216. };
  217. }
  218. });
  219.  
  220. if (data.options.filterMode === 0) {
  221. data.options.filterMode = "隐藏";
  222. } else if (data.options.filterMode === 1) {
  223. data.options.filterMode = "标记";
  224. }
  225.  
  226. saveData();
  227. }
  228.  
  229. // v1.2.x -> v1.3.0
  230. {
  231. if (data.options.keyword) {
  232. addKeyword(data.options.keyword);
  233.  
  234. delete data.options.keyword;
  235.  
  236. saveData();
  237. }
  238. }
  239. }
  240.  
  241. // 编辑用户标记
  242. const editUser = (() => {
  243. let window;
  244. return (uid, name, callback) => {
  245. if (window === undefined) {
  246. window = n.createCommmonWindow();
  247. }
  248.  
  249. const user = data.users[uid];
  250.  
  251. const content = document.createElement("div");
  252.  
  253. const size = Math.floor((screen.width * 0.8) / 200);
  254.  
  255. const items = Object.values(data.tags).map(
  256. (tag, index) => `
  257. <td class="c1">
  258. <label for="s-tag-${index}" style="display: block; cursor: pointer;">
  259. <b class="block_txt nobr" style="background:${
  260. tag.color
  261. }; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>
  262. </label>
  263. </td>
  264. <td class="c2" width="1">
  265. <input id="s-tag-${index}" type="checkbox" value="${tag.id}" ${
  266. user && user.tags.find((item) => item === tag.id) && "checked"
  267. }/>
  268. </td>
  269. `
  270. );
  271.  
  272. const rows = [...new Array(Math.ceil(items.length / size))].map(
  273. (item, index) =>
  274. `
  275. <tr class="row${(index % 2) + 1}">
  276. ${items.slice(size * index, size * (index + 1)).join("")}
  277. </tr>
  278. `
  279. );
  280.  
  281. content.className = "w100";
  282. content.innerHTML = `
  283. <div class="filter-table-wrapper" style="width: 80vw;">
  284. <table class="filter-table forumbox">
  285. <tbody>
  286. ${rows.join("")}
  287. </tbody>
  288. </table>
  289. </div>
  290. <div style="margin: 10px 0;">
  291. <input placeholder="一次性添加多个标记用&quot;|&quot;隔开,不会添加重名标记" style="width: -webkit-fill-available;" />
  292. </div>
  293. <div style="margin: 10px 0;">
  294. <span>过滤方式:</span>
  295. <button>${(user && user.filterMode) || FILTER_MODE[0]}</button>
  296. <div class="right_">
  297. <button>删除</button>
  298. <button>保存</button>
  299. </div>
  300. </div>
  301. <div class="silver" style="margin-top: 5px;">${FILTER_TIPS}</div>
  302. `;
  303.  
  304. const actions = content.getElementsByTagName("button");
  305.  
  306. actions[0].onclick = () => {
  307. actions[0].innerText = switchFilterMode(
  308. actions[0].innerText || FILTER_MODE[0]
  309. );
  310. };
  311.  
  312. actions[1].onclick = () => {
  313. if (confirm("是否确认?")) {
  314. delete data.users[uid];
  315.  
  316. saveData();
  317.  
  318. callback && callback();
  319.  
  320. window._.hide();
  321. }
  322. };
  323.  
  324. actions[2].onclick = () => {
  325. if (confirm("是否确认?")) {
  326. const values = [...content.getElementsByTagName("input")];
  327. const newTags = values[values.length - 1].value
  328. .split("|")
  329. .filter((item) => item.length)
  330. .map((item) => addTag(item));
  331. const tags = [
  332. ...new Set(
  333. values
  334. .filter((item) => item.type === "checkbox" && item.checked)
  335. .map((item) => ~~item.value)
  336. .concat(newTags)
  337. ),
  338. ].sort();
  339.  
  340. if (user) {
  341. user.tags = tags;
  342. user.filterMode = actions[0].innerText;
  343. } else {
  344. addUser(uid, name, tags, actions[0].innerText);
  345. }
  346.  
  347. saveData();
  348.  
  349. callback && callback();
  350.  
  351. window._.hide();
  352. }
  353. };
  354.  
  355. if (user === undefined) {
  356. actions[1].style = "display: none;";
  357. }
  358.  
  359. window._.addContent(null);
  360. window._.addTitle(`编辑标记 - ${name ? name : "#" + uid}`);
  361. window._.addContent(content);
  362. window._.show();
  363. };
  364. })();
  365.  
  366. // 小号过滤和声望过滤
  367. const getFilterModeByUserInfo = async (userInfo, reputation) => {
  368. const filterRegdateLimit = data.options.filterRegdateLimit || 0;
  369.  
  370. const filterPostnumLimit = data.options.filterPostnumLimit || 0;
  371.  
  372. const filterReputationLimit = data.options.filterReputationLimit || NaN;
  373.  
  374. if (userInfo) {
  375. const { regdate, postnum } = userInfo;
  376.  
  377. if (
  378. filterRegdateLimit > 0 &&
  379. regdate * 1000 > new Date() - filterRegdateLimit
  380. ) {
  381. return true;
  382. }
  383.  
  384. if (filterPostnumLimit > 0 && postnum < filterPostnumLimit) {
  385. return true;
  386. }
  387. }
  388.  
  389. if (Number.isNaN(filterReputationLimit) === false) {
  390. if (reputation < filterReputationLimit) {
  391. return true;
  392. }
  393. }
  394.  
  395. return false;
  396. };
  397.  
  398. // 判断过滤方式
  399. const getFilterMode = async (uid, subject, content) => {
  400. let result = -1;
  401.  
  402. const user = data.users[uid];
  403.  
  404. const tags = user ? user.tags.map((tag) => data.tags[tag]) : [];
  405.  
  406. const keywords = Object.values(data.keywords);
  407.  
  408. if (uid && uid > 0) {
  409. const userInfo = n.userInfo.users[uid];
  410.  
  411. const reputation = (() => {
  412. const reputations = n.userInfo.reputations;
  413.  
  414. if (reputations) {
  415. for (let fid in reputations) {
  416. return reputations[fid][uid] || 0;
  417. }
  418. }
  419.  
  420. return NaN;
  421. })();
  422.  
  423. if (await getFilterModeByUserInfo(userInfo, reputation)) {
  424. return FILTER_MODE.indexOf("隐藏");
  425. }
  426. }
  427.  
  428. if (user) {
  429. const filterMode = FILTER_MODE.indexOf(user.filterMode);
  430.  
  431. if (filterMode > 0) {
  432. return filterMode;
  433. }
  434.  
  435. result = filterMode;
  436. }
  437.  
  438. if (tags.length) {
  439. const filterMode = (() => {
  440. if (tags.some((tag) => tag.filterMode !== "显示")) {
  441. return tags
  442. .filter((tag) => tag.filterMode !== "显示")
  443. .map((tag) => FILTER_MODE.indexOf(tag.filterMode) || 0)
  444. .sort((a, b) => b - a)[0];
  445. }
  446.  
  447. return FILTER_MODE.indexOf("显示");
  448. })();
  449.  
  450. if (filterMode > 0) {
  451. return filterMode;
  452. }
  453.  
  454. result = filterMode;
  455. }
  456.  
  457. if (keywords.length) {
  458. const filterMode = (() => {
  459. const sR = (() => {
  460. if (subject) {
  461. const r = keywords
  462. .filter((item) => item.keyword && item.filterMode !== "显示")
  463. .filter((item) => (item.filterLevel || 0) >= 0)
  464. .sort(
  465. (a, b) =>
  466. FILTER_MODE.indexOf(b.filterMode) -
  467. FILTER_MODE.indexOf(a.filterMode)
  468. )
  469. .find((item) => subject.search(item.keyword) >= 0);
  470.  
  471. if (r) {
  472. return FILTER_MODE.indexOf(r.filterMode);
  473. }
  474. }
  475.  
  476. return -1;
  477. })();
  478.  
  479. const cR = (() => {
  480. if (content) {
  481. const r = keywords
  482. .filter((item) => item.keyword && item.filterMode !== "显示")
  483. .filter((item) => (item.filterLevel || 0) >= 1)
  484. .sort(
  485. (a, b) =>
  486. FILTER_MODE.indexOf(b.filterMode) -
  487. FILTER_MODE.indexOf(a.filterMode)
  488. )
  489. .find((item) => content.search(item.keyword) >= 0);
  490.  
  491. if (r) {
  492. return FILTER_MODE.indexOf(r.filterMode);
  493. }
  494. }
  495.  
  496. return -1;
  497. })();
  498.  
  499. return Math.max(sR, cR, result);
  500. })();
  501.  
  502. if (filterMode > 0) {
  503. return filterMode;
  504. }
  505.  
  506. result = filterMode;
  507. }
  508.  
  509. return result;
  510. };
  511.  
  512. // 根据 TID 获取过滤方式
  513. const getFilterModeByTopic = async (tid) => {
  514. return await new Promise((resolve, reject) => {
  515. const api = `/read.php?tid=${tid}`;
  516.  
  517. fetch(api)
  518. .then((res) => res.blob())
  519. .then((blob) => {
  520. const getLastIndex = (content, position) => {
  521. if (position >= 0) {
  522. let nextIndex = position + 1;
  523.  
  524. while (nextIndex < content.length) {
  525. if (content[nextIndex] === "}") {
  526. return nextIndex;
  527. }
  528.  
  529. if (content[nextIndex] === "{") {
  530. nextIndex = getLastIndex(content, nextIndex);
  531.  
  532. if (nextIndex < 0) {
  533. break;
  534. }
  535. }
  536.  
  537. nextIndex = nextIndex + 1;
  538. }
  539. }
  540.  
  541. return -1;
  542. };
  543.  
  544. const reader = new FileReader();
  545.  
  546. reader.onload = async () => {
  547. const parser = new DOMParser();
  548.  
  549. const doc = parser.parseFromString(reader.result, "text/html");
  550.  
  551. const html = doc.body.innerHTML;
  552.  
  553. // 验证帖子正常
  554. const verify = doc.querySelector("#m_posts");
  555.  
  556. if (verify) {
  557. // 取得顶楼 UID
  558. const uid = (() => {
  559. const ele = doc.querySelector("#postauthor0");
  560.  
  561. if (ele) {
  562. const res = ele.getAttribute("href").match(/uid=(\S+)/);
  563.  
  564. if (res) {
  565. return res[1];
  566. }
  567. }
  568.  
  569. return 0;
  570. })();
  571.  
  572. // 取得顶楼标题
  573. const subject = doc.querySelector("#postsubject0").innerHTML;
  574.  
  575. // 取得顶楼内容
  576. const content = doc.querySelector("#postcontent0").innerHTML;
  577.  
  578. if (uid && uid > 0) {
  579. // 取得用户信息
  580. const userInfo = (() => {
  581. // 起始JSON
  582. const str = `"${uid}":{`;
  583.  
  584. // 起始下标
  585. const index = html.indexOf(str) + str.length;
  586.  
  587. // 结尾下标
  588. const lastIndex = getLastIndex(html, index);
  589.  
  590. if (lastIndex >= 0) {
  591. return JSON.parse(`{${html.substring(index, lastIndex)}}`);
  592. }
  593.  
  594. return null;
  595. })();
  596.  
  597. // 取得用户声望
  598. const reputation = (() => {
  599. const reputations = (() => {
  600. // 起始JSON
  601. const str = `"__REPUTATIONS":{`;
  602.  
  603. // 起始下标
  604. const index = html.indexOf(str) + str.length;
  605.  
  606. // 结尾下标
  607. const lastIndex = getLastIndex(html, index);
  608.  
  609. if (lastIndex >= 0) {
  610. return JSON.parse(
  611. `{${html.substring(index, lastIndex)}}`
  612. );
  613. }
  614.  
  615. return null;
  616. })();
  617.  
  618. if (reputations) {
  619. for (let fid in reputations) {
  620. return reputations[fid][uid] || 0;
  621. }
  622. }
  623.  
  624. return NaN;
  625. })();
  626.  
  627. if (await getFilterModeByUserInfo(userInfo, reputation)) {
  628. resolve(FILTER_MODE.indexOf("隐藏"));
  629. }
  630. }
  631.  
  632. resolve(getFilterMode(uid, subject, content));
  633. } else {
  634. reject();
  635. }
  636. };
  637.  
  638. reader.readAsText(blob, "GBK");
  639. })
  640. .catch(() => {
  641. reject();
  642. });
  643. }).catch(() => {
  644. return FILTER_MODE.indexOf("隐藏");
  645. });
  646. };
  647.  
  648. // 处理引用
  649. const handleQuote = async (content) => {
  650. const quotes = content.querySelectorAll(".quote");
  651.  
  652. await Promise.all(
  653. [...quotes].map(async (quote) => {
  654. const uid = (() => {
  655. const ele = quote.querySelector("a[href^='/nuke.php']");
  656.  
  657. if (ele) {
  658. const res = ele.getAttribute("href").match(/uid=(\S+)/);
  659.  
  660. if (res) {
  661. return res[1];
  662. }
  663. }
  664.  
  665. return 0;
  666. })();
  667.  
  668. const filterMode = await new Promise(async (resolve) => {
  669. const mode = await getFilterMode(uid, "", quote.innerText);
  670.  
  671. if (mode === 0) {
  672. resolve(data.options.filterMode);
  673. }
  674.  
  675. if (mode > 0) {
  676. resolve(FILTER_MODE[mode]);
  677. }
  678.  
  679. resolve("");
  680. });
  681.  
  682. if (filterMode === "标记") {
  683. quote.innerHTML = `
  684. <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
  685. <span class="crimson">Troll must die.</span>
  686. <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
  687. <div style="display: none;" name="troll_${uid}">
  688. ${quote.innerHTML}
  689. </div>
  690. </div>`;
  691. } else if (filterMode === "遮罩") {
  692. const source = document.createElement("DIV");
  693.  
  694. source.innerHTML = quote.innerHTML;
  695. source.style.display = "none";
  696.  
  697. const caption = document.createElement("CAPTION");
  698.  
  699. caption.className = "filter-mask filter-mask-block";
  700.  
  701. caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
  702. caption.onclick = () => {
  703. quote.removeChild(caption);
  704.  
  705. source.style.display = "";
  706. };
  707.  
  708. quote.innerHTML = "";
  709. quote.appendChild(source);
  710. quote.appendChild(caption);
  711. } else if (filterMode === "隐藏") {
  712. quote.innerHTML = "";
  713. }
  714. })
  715. );
  716. };
  717.  
  718. // 过滤
  719. const reFilter = (() => {
  720. let hasNext = false;
  721. let isRunning = false;
  722.  
  723. const func = async () => {
  724. const tPage = location.pathname === "/thread.php";
  725. const pPage = location.pathname === "/read.php";
  726.  
  727. if (tPage && new RegExp(`authorid=${self}`).test(location.search)) {
  728. return;
  729. }
  730.  
  731. if (tPage) {
  732. const tData = n.topicArg.data;
  733.  
  734. await Promise.all(
  735. Object.values(tData).map(async (item) => {
  736. if (item.containerC) return;
  737.  
  738. const tid = item[8];
  739.  
  740. const filterMode = await new Promise(async (resolve) => {
  741. const mode = await getFilterModeByTopic(tid);
  742.  
  743. if (mode === 0) {
  744. resolve(data.options.filterMode);
  745. }
  746.  
  747. if (mode > 0) {
  748. resolve(FILTER_MODE[mode]);
  749. }
  750.  
  751. resolve("");
  752. });
  753.  
  754. item.contentC = item[1];
  755.  
  756. item.contentB = item.contentB || item.contentC.innerHTML;
  757.  
  758. item.containerC =
  759. item.containerC || item.contentC.parentNode.parentNode;
  760.  
  761. item.containerC.style = "";
  762. item.contentC.style = "";
  763. item[1].className = item[1].className.replace(" filter-mask", "");
  764. item[2].className = item[2].className.replace(" filter-mask", "");
  765.  
  766. if (filterMode === "标记") {
  767. item.contentC.style = "text-decoration: line-through;";
  768. } else if (filterMode === "遮罩") {
  769. item[1].className += " filter-mask";
  770. item[2].className += " filter-mask";
  771. } else if (filterMode === "隐藏") {
  772. item.containerC.style = "display: none;";
  773. }
  774. })
  775. );
  776. } else if (pPage) {
  777. const pData = n.postArg.data;
  778.  
  779. await Promise.all(
  780. Object.values(pData).map(async (item) => {
  781. if (~~item.pAid === self) return;
  782. if (item.containerC) return;
  783.  
  784. if (typeof item.i === "number") {
  785. item.actionC =
  786. item.actionC ||
  787. (() => {
  788. const ele = item.uInfoC.querySelector('[name="uid"]');
  789.  
  790. ele.onclick = null;
  791.  
  792. return ele;
  793. })();
  794.  
  795. item.tagC =
  796. item.tagC ||
  797. (() => {
  798. const tc = document.createElement("div");
  799.  
  800. tc.className = "filter-tags";
  801.  
  802. item.uInfoC.appendChild(tc);
  803.  
  804. return tc;
  805. })();
  806. }
  807.  
  808. item.pName =
  809. item.pName ||
  810. item.uInfoC.getElementsByClassName("author")[0].innerText;
  811.  
  812. item.reFilter =
  813. item.reFilter ||
  814. (async () => {
  815. const uid = item.pAid;
  816.  
  817. const filterMode = await new Promise(async (resolve) => {
  818. const mode = await getFilterMode(
  819. uid,
  820. item.subjectC.innerText,
  821. item.contentC.innerText
  822. );
  823.  
  824. if (mode === 0) {
  825. resolve(data.options.filterMode);
  826. }
  827.  
  828. if (mode > 0) {
  829. resolve(FILTER_MODE[mode]);
  830. }
  831.  
  832. resolve("");
  833. });
  834.  
  835. item.avatarC =
  836. item.avatarC ||
  837. (() => {
  838. const tc = document.createElement("div");
  839.  
  840. const avatar = document.getElementById(
  841. `posteravatar${item.i}`
  842. );
  843.  
  844. if (avatar) {
  845. avatar.parentNode.insertBefore(tc, avatar.nextSibling);
  846.  
  847. tc.appendChild(avatar);
  848. }
  849.  
  850. return tc;
  851. })();
  852.  
  853. item.contentB = item.contentB || item.contentC.innerHTML;
  854.  
  855. item.containerC =
  856. item.containerC ||
  857. (() => {
  858. let temp = item.contentC;
  859.  
  860. if (item.i >= 0) {
  861. while (temp.nodeName !== "TBODY") {
  862. temp = temp.parentNode;
  863. }
  864. } else {
  865. while (temp.nodeName !== "DIV") {
  866. temp = temp.parentNode;
  867. }
  868. }
  869.  
  870. return temp;
  871. })();
  872.  
  873. item.avatarC.style.display = "";
  874. item.containerC.style.display = "";
  875. item.contentC.innerHTML = item.contentB;
  876.  
  877. if (item.actionC) {
  878. item.actionC.style = "background: #aaa;";
  879. }
  880.  
  881. if (filterMode === "标记") {
  882. item.avatarC.style.display = "none";
  883. item.contentC.innerHTML = `
  884. <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
  885. <span class="crimson">Troll must die.</span>
  886. <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
  887. <div style="display: none;" name="troll_${uid}">
  888. ${item.contentB}
  889. </div>
  890. </div>`;
  891.  
  892. if (item.actionC && data.users[uid]) {
  893. item.actionC.style = "background: #cb4042;";
  894. }
  895. } else if (filterMode === "遮罩") {
  896. const caption = document.createElement("CAPTION");
  897.  
  898. if (item.i >= 0) {
  899. caption.className = "filter-mask filter-mask-block";
  900. } else {
  901. caption.className = "filter-mask filter-mask-block left";
  902. caption.style = "width: 47%;";
  903. }
  904.  
  905. caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
  906. caption.onclick = () => {
  907. item.containerC.parentNode.removeChild(caption);
  908. item.containerC.style.display = "";
  909. };
  910.  
  911. item.containerC.parentNode.insertBefore(
  912. caption,
  913. item.containerC
  914. );
  915. item.containerC.style.display = "none";
  916.  
  917. if (item.actionC && data.users[uid]) {
  918. item.actionC.style = "background: #cb4042;";
  919. }
  920. } else if (filterMode === "隐藏") {
  921. item.containerC.style.display = "none";
  922. } else {
  923. await handleQuote(item.contentC);
  924. }
  925.  
  926. if (item.tagC) {
  927. const tags = data.users[uid]
  928. ? data.users[uid].tags.map((tag) => data.tags[tag]) || []
  929. : [];
  930.  
  931. item.tagC.style.display = tags.length ? "" : "none";
  932. item.tagC.innerHTML = tags
  933. .map(
  934. (tag) =>
  935. `<b class="block_txt nobr" style="background:${tag.color}; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>`
  936. )
  937. .join("");
  938. }
  939. });
  940.  
  941. if (item.actionC) {
  942. item.actionC.onclick =
  943. item.actionC.onclick ||
  944. ((e) => {
  945. if (item.pAid < 0) return;
  946.  
  947. const user = data.users[item.pAid];
  948.  
  949. if (e.ctrlKey === false) {
  950. editUser(item.pAid, item.pName, item.reFilter);
  951. } else {
  952. if (user) {
  953. delete data.users[user.id];
  954. } else {
  955. addUser(item.pAid, item.pName);
  956. }
  957.  
  958. saveData();
  959. item.reFilter();
  960. }
  961. });
  962. }
  963.  
  964. await item.reFilter();
  965. })
  966. );
  967. }
  968. };
  969.  
  970. const execute = () =>
  971. func().finally(() => {
  972. if (hasNext) {
  973. hasNext = false;
  974.  
  975. execute();
  976. } else {
  977. isRunning = false;
  978. }
  979. });
  980.  
  981. return async () => {
  982. if (isRunning) {
  983. hasNext = true;
  984. } else {
  985. isRunning = true;
  986.  
  987. await execute();
  988. }
  989. };
  990. })();
  991.  
  992. // STYLE
  993. GM_addStyle(`
  994. .filter-table-wrapper {
  995. max-height: 80vh;
  996. overflow-y: auto;
  997. }
  998. .filter-table {
  999. margin: 0;
  1000. }
  1001. .filter-table th,
  1002. .filter-table td {
  1003. position: relative;
  1004. white-space: nowrap;
  1005. }
  1006. .filter-table th {
  1007. position: sticky;
  1008. top: 2px;
  1009. z-index: 1;
  1010. }
  1011. .filter-table input:not([type]), .filter-table input[type="text"] {
  1012. margin: 0;
  1013. box-sizing: border-box;
  1014. height: 100%;
  1015. width: 100%;
  1016. }
  1017. .filter-input-wrapper {
  1018. position: absolute;
  1019. top: 6px;
  1020. right: 6px;
  1021. bottom: 6px;
  1022. left: 6px;
  1023. }
  1024. .filter-text-ellipsis {
  1025. display: flex;
  1026. }
  1027. .filter-text-ellipsis > * {
  1028. flex: 1;
  1029. width: 1px;
  1030. overflow: hidden;
  1031. text-overflow: ellipsis;
  1032. }
  1033. .filter-button-group {
  1034. margin: -.1em -.2em;
  1035. }
  1036. .filter-tags {
  1037. margin: 2px -0.2em 0;
  1038. text-align: left;
  1039. }
  1040. .filter-mask {
  1041. margin: 1px;
  1042. color: #81C7D4;
  1043. background: #81C7D4;
  1044. }
  1045. .filter-mask-block {
  1046. display: block;
  1047. border: 1px solid #66BAB7;
  1048. text-align: center !important;
  1049. }
  1050. .filter-input-wrapper {
  1051. position: absolute;
  1052. top: 6px;
  1053. right: 6px;
  1054. bottom: 6px;
  1055. left: 6px;
  1056. }
  1057. `);
  1058.  
  1059. // UI
  1060. const u = (() => {
  1061. const modules = {};
  1062.  
  1063. const tabContainer = (() => {
  1064. const c = document.createElement("div");
  1065.  
  1066. c.className = "w100";
  1067. c.innerHTML = `
  1068. <div class="right_" style="margin-bottom: 5px;">
  1069. <table class="stdbtn" cellspacing="0">
  1070. <tbody>
  1071. <tr></tr>
  1072. </tbody>
  1073. </table>
  1074. </div>
  1075. <div class="clear"></div>
  1076. `;
  1077.  
  1078. return c;
  1079. })();
  1080.  
  1081. const tabPanelContainer = (() => {
  1082. const c = document.createElement("div");
  1083.  
  1084. c.style = "width: 80vw;";
  1085.  
  1086. return c;
  1087. })();
  1088.  
  1089. const content = (() => {
  1090. const c = document.createElement("div");
  1091.  
  1092. c.append(tabContainer);
  1093. c.append(tabPanelContainer);
  1094.  
  1095. return c;
  1096. })();
  1097.  
  1098. const addModule = (() => {
  1099. const tc = tabContainer.getElementsByTagName("tr")[0];
  1100. const cc = tabPanelContainer;
  1101.  
  1102. return (module) => {
  1103. const tabBox = document.createElement("td");
  1104.  
  1105. tabBox.innerHTML = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`;
  1106.  
  1107. const tab = tabBox.childNodes[0];
  1108.  
  1109. const toggle = () => {
  1110. Object.values(modules).forEach((item) => {
  1111. if (item.tab === tab) {
  1112. item.tab.className = "nobr";
  1113. item.content.style = "display: block";
  1114. item.refresh();
  1115. } else {
  1116. item.tab.className = "nobr silver";
  1117. item.content.style = "display: none";
  1118. }
  1119. });
  1120. };
  1121.  
  1122. tc.append(tabBox);
  1123. cc.append(module.content);
  1124.  
  1125. tab.onclick = toggle;
  1126.  
  1127. modules[module.name] = {
  1128. ...module,
  1129. tab,
  1130. toggle,
  1131. };
  1132.  
  1133. return modules[module.name];
  1134. };
  1135. })();
  1136.  
  1137. return {
  1138. content,
  1139. modules,
  1140. addModule,
  1141. };
  1142. })();
  1143.  
  1144. // 用户
  1145. const userModule = (() => {
  1146. const content = (() => {
  1147. const c = document.createElement("div");
  1148.  
  1149. c.style = "display: none";
  1150. c.innerHTML = `
  1151. <div class="filter-table-wrapper">
  1152. <table class="filter-table forumbox">
  1153. <thead>
  1154. <tr class="block_txt_c0">
  1155. <th class="c1" width="1">昵称</th>
  1156. <th class="c2">标记</th>
  1157. <th class="c3" width="1">过滤方式</th>
  1158. <th class="c4" width="1">操作</th>
  1159. </tr>
  1160. </thead>
  1161. <tbody></tbody>
  1162. </table>
  1163. </div>
  1164. `;
  1165.  
  1166. return c;
  1167. })();
  1168.  
  1169. const refresh = (() => {
  1170. const container = content.getElementsByTagName("tbody")[0];
  1171.  
  1172. const func = () => {
  1173. container.innerHTML = "";
  1174.  
  1175. Object.values(data.users).forEach((item) => {
  1176. const tc = document.createElement("tr");
  1177.  
  1178. tc.className = `row${
  1179. (container.querySelectorAll("TR").length % 2) + 1
  1180. }`;
  1181.  
  1182. tc.refresh = () => {
  1183. if (data.users[item.id]) {
  1184. tc.innerHTML = `
  1185. <td class="c1">
  1186. <a href="/nuke.php?func=ucp&uid=${
  1187. item.id
  1188. }" class="b nobr">[${
  1189. item.name ? "@" + item.name : "#" + item.id
  1190. }]</a>
  1191. </td>
  1192. <td class="c2">
  1193. ${item.tags
  1194. .map((tag) => {
  1195. if (data.tags[tag]) {
  1196. return `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`;
  1197. }
  1198. })
  1199. .join("")}
  1200. </td>
  1201. <td class="c3">
  1202. <div class="filter-table-button-group">
  1203. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1204. </div>
  1205. </td>
  1206. <td class="c4">
  1207. <div class="filter-table-button-group">
  1208. <button>编辑</button>
  1209. <button>删除</button>
  1210. </div>
  1211. </td>
  1212. `;
  1213.  
  1214. const actions = tc.getElementsByTagName("button");
  1215.  
  1216. actions[0].onclick = () => {
  1217. data.users[item.id].filterMode = switchFilterMode(
  1218. data.users[item.id].filterMode || FILTER_MODE[0]
  1219. );
  1220.  
  1221. actions[0].innerHTML = data.users[item.id].filterMode;
  1222.  
  1223. saveData();
  1224. reFilter();
  1225. };
  1226.  
  1227. actions[1].onclick = () => {
  1228. editUser(item.id, item.name, tc.refresh);
  1229. };
  1230.  
  1231. actions[2].onclick = () => {
  1232. if (confirm("是否确认?")) {
  1233. delete data.users[item.id];
  1234. container.removeChild(tc);
  1235.  
  1236. saveData();
  1237. reFilter();
  1238. }
  1239. };
  1240. } else {
  1241. tc.remove();
  1242. }
  1243. };
  1244.  
  1245. tc.refresh();
  1246.  
  1247. container.appendChild(tc);
  1248. });
  1249. };
  1250.  
  1251. return func;
  1252. })();
  1253.  
  1254. return {
  1255. name: "用户",
  1256. content,
  1257. refresh,
  1258. };
  1259. })();
  1260.  
  1261. // 标记
  1262. const tagModule = (() => {
  1263. const content = (() => {
  1264. const c = document.createElement("div");
  1265.  
  1266. c.style = "display: none";
  1267. c.innerHTML = `
  1268. <div class="filter-table-wrapper">
  1269. <table class="filter-table forumbox">
  1270. <thead>
  1271. <tr class="block_txt_c0">
  1272. <th class="c1" width="1">标记</th>
  1273. <th class="c2">列表</th>
  1274. <th class="c3" width="1">过滤方式</th>
  1275. <th class="c4" width="1">操作</th>
  1276. </tr>
  1277. </thead>
  1278. <tbody></tbody>
  1279. </table>
  1280. </div>
  1281. `;
  1282.  
  1283. return c;
  1284. })();
  1285.  
  1286. const refresh = (() => {
  1287. const container = content.getElementsByTagName("tbody")[0];
  1288.  
  1289. const func = () => {
  1290. container.innerHTML = "";
  1291.  
  1292. Object.values(data.tags).forEach((item) => {
  1293. const tc = document.createElement("tr");
  1294.  
  1295. tc.className = `row${
  1296. (container.querySelectorAll("TR").length % 2) + 1
  1297. }`;
  1298.  
  1299. tc.innerHTML = `
  1300. <td class="c1">
  1301. <b class="block_txt nobr" style="background:${
  1302. item.color
  1303. }; color:#fff; margin: 0.1em 0.2em;">${item.name}</b>
  1304. </td>
  1305. <td class="c2">
  1306. <button>${
  1307. Object.values(data.users).filter((user) =>
  1308. user.tags.find((tag) => tag === item.id)
  1309. ).length
  1310. }
  1311. </button>
  1312. <div style="white-space: normal; display: none;">
  1313. ${Object.values(data.users)
  1314. .filter((user) =>
  1315. user.tags.find((tag) => tag === item.id)
  1316. )
  1317. .map(
  1318. (user) =>
  1319. `<a href="/nuke.php?func=ucp&uid=${
  1320. user.id
  1321. }" class="b nobr">[${
  1322. user.name ? "@" + user.name : "#" + user.id
  1323. }]</a>`
  1324. )
  1325. .join("")}
  1326. </div>
  1327. </td>
  1328. <td class="c3">
  1329. <div class="filter-table-button-group">
  1330. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1331. </div>
  1332. </td>
  1333. <td class="c4">
  1334. <div class="filter-table-button-group">
  1335. <button>删除</button>
  1336. </div>
  1337. </td>
  1338. `;
  1339.  
  1340. const actions = tc.getElementsByTagName("button");
  1341.  
  1342. actions[0].onclick = (() => {
  1343. let hide = true;
  1344. return () => {
  1345. hide = !hide;
  1346. actions[0].nextElementSibling.style.display = hide
  1347. ? "none"
  1348. : "block";
  1349. };
  1350. })();
  1351.  
  1352. actions[1].onclick = () => {
  1353. data.tags[item.id].filterMode = switchFilterMode(
  1354. data.tags[item.id].filterMode || FILTER_MODE[0]
  1355. );
  1356.  
  1357. actions[1].innerHTML = data.tags[item.id].filterMode;
  1358.  
  1359. saveData();
  1360. reFilter();
  1361. };
  1362.  
  1363. actions[2].onclick = () => {
  1364. if (confirm("是否确认?")) {
  1365. delete data.tags[item.id];
  1366.  
  1367. Object.values(data.users).forEach((user) => {
  1368. const index = user.tags.findIndex((tag) => tag === item.id);
  1369. if (index >= 0) {
  1370. user.tags.splice(index, 1);
  1371. }
  1372. });
  1373.  
  1374. container.removeChild(tc);
  1375.  
  1376. saveData();
  1377. reFilter();
  1378. }
  1379. };
  1380.  
  1381. container.appendChild(tc);
  1382. });
  1383. };
  1384.  
  1385. return func;
  1386. })();
  1387.  
  1388. return {
  1389. name: "标记",
  1390. content,
  1391. refresh,
  1392. };
  1393. })();
  1394.  
  1395. // 关键字
  1396. const keywordModule = (() => {
  1397. const content = (() => {
  1398. const c = document.createElement("div");
  1399.  
  1400. c.style = "display: none";
  1401. c.innerHTML = `
  1402. <div class="filter-table-wrapper">
  1403. <table class="filter-table forumbox">
  1404. <thead>
  1405. <tr class="block_txt_c0">
  1406. <th class="c1">列表</th>
  1407. <th class="c2" width="1">过滤方式</th>
  1408. <th class="c3" width="1">包括内容</th>
  1409. <th class="c4" width="1">操作</th>
  1410. </tr>
  1411. </thead>
  1412. <tbody></tbody>
  1413. </table>
  1414. </div>
  1415. <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用&quot;|&quot;隔开,&quot;ABC|DEF&quot;即为屏蔽带有ABC或者DEF的内容。</div>
  1416. `;
  1417.  
  1418. return c;
  1419. })();
  1420.  
  1421. const refresh = (() => {
  1422. const container = content.getElementsByTagName("tbody")[0];
  1423.  
  1424. const func = () => {
  1425. container.innerHTML = "";
  1426.  
  1427. Object.values(data.keywords).forEach((item) => {
  1428. const tc = document.createElement("tr");
  1429.  
  1430. tc.className = `row${
  1431. (container.querySelectorAll("TR").length % 2) + 1
  1432. }`;
  1433.  
  1434. tc.innerHTML = `
  1435. <td class="c1">
  1436. <div class="filter-input-wrapper">
  1437. <input value="${item.keyword || ""}" />
  1438. </div>
  1439. </td>
  1440. <td class="c2">
  1441. <div class="filter-table-button-group">
  1442. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1443. </div>
  1444. </td>
  1445. <td class="c3">
  1446. <div style="text-align: center;">
  1447. <input type="checkbox" ${
  1448. item.filterLevel ? `checked="checked"` : ""
  1449. } />
  1450. </div>
  1451. </td>
  1452. <td class="c4">
  1453. <div class="filter-table-button-group">
  1454. <button>保存</button>
  1455. <button>删除</button>
  1456. </div>
  1457. </td>
  1458. `;
  1459.  
  1460. const inputElement = tc.querySelector("INPUT");
  1461. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1462. const actions = tc.getElementsByTagName("button");
  1463.  
  1464. actions[0].onclick = () => {
  1465. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1466. };
  1467.  
  1468. actions[1].onclick = () => {
  1469. if (inputElement.value) {
  1470. data.keywords[item.id] = {
  1471. id: item.id,
  1472. keyword: inputElement.value,
  1473. filterMode: actions[0].innerHTML,
  1474. filterLevel: levelElement.checked ? 1 : 0,
  1475. };
  1476.  
  1477. saveData();
  1478. refresh();
  1479. }
  1480. };
  1481.  
  1482. actions[2].onclick = () => {
  1483. if (confirm("是否确认?")) {
  1484. delete data.keywords[item.id];
  1485.  
  1486. saveData();
  1487. refresh();
  1488. }
  1489. };
  1490.  
  1491. container.appendChild(tc);
  1492. });
  1493.  
  1494. {
  1495. const tc = document.createElement("tr");
  1496.  
  1497. tc.className = `row${
  1498. (container.querySelectorAll("TR").length % 2) + 1
  1499. }`;
  1500.  
  1501. tc.innerHTML = `
  1502. <td class="c1">
  1503. <div class="filter-input-wrapper">
  1504. <input value="" />
  1505. </div>
  1506. </td>
  1507. <td class="c2">
  1508. <div class="filter-table-button-group">
  1509. <button>${FILTER_MODE[0]}</button>
  1510. </div>
  1511. </td>
  1512. <td class="c3">
  1513. <div style="text-align: center;">
  1514. <input type="checkbox" />
  1515. </div>
  1516. </td>
  1517. <td class="c4">
  1518. <div class="filter-table-button-group">
  1519. <button>添加</button>
  1520. </div>
  1521. </td>
  1522. `;
  1523.  
  1524. const inputElement = tc.querySelector("INPUT");
  1525. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1526. const actions = tc.getElementsByTagName("button");
  1527.  
  1528. actions[0].onclick = () => {
  1529. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1530. };
  1531.  
  1532. actions[1].onclick = () => {
  1533. if (inputElement.value) {
  1534. addKeyword(
  1535. inputElement.value,
  1536. actions[0].innerHTML,
  1537. levelElement.checked ? 1 : 0
  1538. );
  1539.  
  1540. saveData();
  1541. refresh();
  1542. }
  1543. };
  1544.  
  1545. container.appendChild(tc);
  1546. }
  1547. };
  1548.  
  1549. return func;
  1550. })();
  1551.  
  1552. return {
  1553. name: "关键字",
  1554. content,
  1555. refresh,
  1556. };
  1557. })();
  1558.  
  1559. // 通用设置
  1560. const commonModule = (() => {
  1561. const content = (() => {
  1562. const c = document.createElement("div");
  1563.  
  1564. c.style = "display: none";
  1565.  
  1566. return c;
  1567. })();
  1568.  
  1569. const refresh = (() => {
  1570. const container = content;
  1571.  
  1572. const func = () => {
  1573. container.innerHTML = "";
  1574.  
  1575. // 默认过滤方式
  1576. {
  1577. const tc = document.createElement("div");
  1578.  
  1579. tc.innerHTML += `
  1580. <div>默认过滤方式</div>
  1581. <div></div>
  1582. <div class="silver" style="margin-top: 10px;">${FILTER_TIPS}</div>
  1583. `;
  1584.  
  1585. ["标记", "遮罩", "隐藏"].forEach((item, index) => {
  1586. const ele = document.createElement("SPAN");
  1587.  
  1588. ele.innerHTML += `
  1589. <input id="s-fm-${index}" type="radio" name="filterType" ${
  1590. data.options.filterMode === item && "checked"
  1591. }>
  1592. <label for="s-fm-${index}" style="cursor: pointer;">${item}</label>
  1593. `;
  1594.  
  1595. const inp = ele.querySelector("input");
  1596.  
  1597. inp.onchange = () => {
  1598. if (inp.checked) {
  1599. data.options.filterMode = item;
  1600. saveData();
  1601. reFilter();
  1602. }
  1603. };
  1604.  
  1605. tc.querySelectorAll("div")[1].append(ele);
  1606. });
  1607.  
  1608. container.appendChild(tc);
  1609. }
  1610.  
  1611. // 小号过滤(时间)
  1612. {
  1613. const tc = document.createElement("div");
  1614.  
  1615. tc.innerHTML += `
  1616. <br/>
  1617. <div>
  1618. 隐藏注册时间小于<input value="${
  1619. (data.options.filterRegdateLimit || 0) / 86400000
  1620. }" maxLength="4" style="width: 48px;" />天的用户
  1621. <button>确认</button>
  1622. </div>
  1623. `;
  1624.  
  1625. const actions = tc.getElementsByTagName("button");
  1626.  
  1627. actions[0].onclick = () => {
  1628. const v = actions[0].previousElementSibling.value;
  1629.  
  1630. const n = Number(v) || 0;
  1631.  
  1632. data.options.filterRegdateLimit = n < 0 ? 0 : n * 86400000;
  1633.  
  1634. saveData();
  1635. reFilter();
  1636. };
  1637.  
  1638. container.appendChild(tc);
  1639. }
  1640.  
  1641. // 小号过滤(发帖数)
  1642. {
  1643. const tc = document.createElement("div");
  1644.  
  1645. tc.innerHTML += `
  1646. <br/>
  1647. <div>
  1648. 隐藏发帖数量小于<input value="${
  1649. data.options.filterPostnumLimit || 0
  1650. }" maxLength="5" style="width: 48px;" />贴的用户
  1651. <button>确认</button>
  1652. </div>
  1653. `;
  1654.  
  1655. const actions = tc.getElementsByTagName("button");
  1656.  
  1657. actions[0].onclick = () => {
  1658. const v = actions[0].previousElementSibling.value;
  1659.  
  1660. const n = Number(v) || 0;
  1661.  
  1662. data.options.filterPostnumLimit = n < 0 ? 0 : n;
  1663.  
  1664. saveData();
  1665. reFilter();
  1666. };
  1667.  
  1668. container.appendChild(tc);
  1669. }
  1670.  
  1671. // 声望过滤
  1672. {
  1673. const tc = document.createElement("div");
  1674.  
  1675. tc.innerHTML += `
  1676. <br/>
  1677. <div>
  1678. 隐藏版面声望低于<input value="${
  1679. data.options.filterReputationLimit || ""
  1680. }" maxLength="5" style="width: 48px;" />点的用户
  1681. <button>确认</button>
  1682. </div>
  1683. `;
  1684.  
  1685. const actions = tc.getElementsByTagName("button");
  1686.  
  1687. actions[0].onclick = () => {
  1688. const v = actions[0].previousElementSibling.value;
  1689.  
  1690. const n = Number(v);
  1691.  
  1692. data.options.filterReputationLimit = n;
  1693.  
  1694. saveData();
  1695. reFilter();
  1696. };
  1697.  
  1698. container.appendChild(tc);
  1699. }
  1700.  
  1701. // 删除没有标记的用户
  1702. {
  1703. const tc = document.createElement("div");
  1704.  
  1705. tc.innerHTML += `
  1706. <br/>
  1707. <div>
  1708. <button>删除没有标记的用户</button>
  1709. </div>
  1710. `;
  1711.  
  1712. const actions = tc.getElementsByTagName("button");
  1713.  
  1714. actions[0].onclick = () => {
  1715. if (confirm("是否确认?")) {
  1716. Object.values(data.users).forEach((item) => {
  1717. if (item.tags.length === 0) {
  1718. delete data.users[item.id];
  1719. }
  1720. });
  1721.  
  1722. saveData();
  1723. reFilter();
  1724. }
  1725. };
  1726.  
  1727. container.appendChild(tc);
  1728. }
  1729.  
  1730. // 删除没有用户的标记
  1731. {
  1732. const tc = document.createElement("div");
  1733.  
  1734. tc.innerHTML += `
  1735. <br/>
  1736. <div>
  1737. <button>删除没有用户的标记</button>
  1738. </div>
  1739. `;
  1740.  
  1741. const actions = tc.getElementsByTagName("button");
  1742.  
  1743. actions[0].onclick = () => {
  1744. if (confirm("是否确认?")) {
  1745. Object.values(data.tags).forEach((item) => {
  1746. if (
  1747. Object.values(data.users).filter((user) =>
  1748. user.tags.find((tag) => tag === item.id)
  1749. ).length === 0
  1750. ) {
  1751. delete data.tags[item.id];
  1752. }
  1753. });
  1754.  
  1755. saveData();
  1756. reFilter();
  1757. }
  1758. };
  1759.  
  1760. container.appendChild(tc);
  1761. }
  1762. };
  1763.  
  1764. return func;
  1765. })();
  1766.  
  1767. return {
  1768. name: "通用设置",
  1769. content,
  1770. refresh,
  1771. };
  1772. })();
  1773.  
  1774. u.addModule(userModule).toggle();
  1775. u.addModule(tagModule);
  1776. u.addModule(keywordModule);
  1777. u.addModule(commonModule);
  1778.  
  1779. // 增加菜单项
  1780. (() => {
  1781. const title = "过滤设置";
  1782.  
  1783. let window;
  1784.  
  1785. const container = document.createElement("DIV");
  1786.  
  1787. container.className = `td`;
  1788. container.innerHTML = `<a class="mmdefault" href="javascript: void(0);" style="white-space: nowrap;">屏蔽</a>`;
  1789.  
  1790. const content = container.querySelector("A");
  1791.  
  1792. const anchor = document.querySelector("#mainmenu .td:last-child");
  1793.  
  1794. anchor.before(container);
  1795.  
  1796. content.onclick = () => {
  1797. if (window === undefined) {
  1798. window = n.createCommmonWindow();
  1799. }
  1800.  
  1801. window._.addContent(null);
  1802. window._.addTitle(title);
  1803. window._.addContent(u.content);
  1804. window._.show();
  1805. };
  1806. })();
  1807.  
  1808. // 执行过滤
  1809. (() => {
  1810. const hookFunction = (object, functionName, callback) => {
  1811. ((originalFunction) => {
  1812. object[functionName] = function () {
  1813. const returnValue = originalFunction.apply(this, arguments);
  1814.  
  1815. callback.apply(this, [returnValue, originalFunction, arguments]);
  1816.  
  1817. return returnValue;
  1818. };
  1819. })(object[functionName]);
  1820. };
  1821.  
  1822. const initialized = {
  1823. topicArg: false,
  1824. postArg: false,
  1825. };
  1826.  
  1827. hookFunction(n, "eval", () => {
  1828. if (Object.values(initialized).findIndex((item) => item === false) < 0) {
  1829. return;
  1830. }
  1831.  
  1832. if (n.topicArg && initialized.topicArg === false) {
  1833. hookFunction(n.topicArg, "add", reFilter);
  1834.  
  1835. initialized.topicArg = true;
  1836. }
  1837.  
  1838. if (n.postArg && initialized.postArg === false) {
  1839. hookFunction(n.postArg, "proc", reFilter);
  1840.  
  1841. initialized.postArg = true;
  1842. }
  1843. });
  1844.  
  1845. reFilter();
  1846. })();
  1847. })(commonui, __CURRENT_UID);