NGA Filter

troll must die

目前為 2022-04-29 提交的版本,檢視 最新版本

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