NGA Filter

troll must die

当前为 2022-03-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name NGA Filter
  3. // @namespace https://greasyfork.org/users/263018
  4. // @version 1.5.2
  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. if (userInfo) {
  383. if (
  384. (userInfo.regdate * 1000 > new Date() - filterRegdateLimit) ||
  385. (userInfo.postnum && userInfo.postnum < filterPostnumLimit) ||
  386. (userInfo.posts && userInfo.posts < filterPostnumLimit)
  387. ) {
  388. return FILTER_MODE.indexOf("隐藏");
  389. }
  390. }
  391. }
  392. }
  393.  
  394. if (user) {
  395. const filterMode = FILTER_MODE.indexOf(user.filterMode);
  396.  
  397. if (filterMode > 0) {
  398. return filterMode;
  399. }
  400.  
  401. result = filterMode;
  402. }
  403.  
  404. if (tags.length) {
  405. const filterMode = (() => {
  406. if (tags.some((tag) => tag.filterMode !== "显示")) {
  407. return tags
  408. .filter((tag) => tag.filterMode !== "显示")
  409. .map((tag) => FILTER_MODE.indexOf(tag.filterMode) || 0)
  410. .sort((a, b) => b - a)[0];
  411. }
  412.  
  413. return FILTER_MODE.indexOf("显示");
  414. })();
  415.  
  416. if (filterMode > 0) {
  417. return filterMode;
  418. }
  419.  
  420. result = filterMode;
  421. }
  422.  
  423. if (keywords.length) {
  424. const filterMode = (() => {
  425. const sR = (() => {
  426. if (subject) {
  427. const r = keywords
  428. .filter((item) => item.keyword && item.filterMode !== "显示")
  429. .filter((item) => (item.filterLevel || 0) >= 0)
  430. .sort(
  431. (a, b) =>
  432. FILTER_MODE.indexOf(b.filterMode) -
  433. FILTER_MODE.indexOf(a.filterMode)
  434. )
  435. .find((item) => subject.search(item.keyword) >= 0);
  436.  
  437. if (r) {
  438. return FILTER_MODE.indexOf(r.filterMode);
  439. }
  440. }
  441. return -1;
  442. })();
  443. const cR = (() => {
  444. if (content) {
  445. const r = keywords
  446. .filter((item) => item.keyword && item.filterMode !== "显示")
  447. .filter((item) => (item.filterLevel || 0) >= 1)
  448. .sort(
  449. (a, b) =>
  450. FILTER_MODE.indexOf(b.filterMode) -
  451. FILTER_MODE.indexOf(a.filterMode)
  452. )
  453. .find((item) => content.search(item.keyword) >= 0);
  454.  
  455. if (r) {
  456. return FILTER_MODE.indexOf(r.filterMode);
  457. }
  458. }
  459. return -1;
  460. })();
  461.  
  462. return Math.max(sR, cR, result);
  463. })();
  464.  
  465. if (filterMode > 0) {
  466. return filterMode;
  467. }
  468.  
  469. result = filterMode;
  470. }
  471.  
  472. return result;
  473. };
  474. // 根据 TID 获取过滤方式
  475. const getFilterModeByTopic = async (uid, tid, temp) => {
  476. const tInfo = await new Promise((resolve) => {
  477. const api = uid ?
  478. `/read.php?tid=${tid}&authorid=${uid}&lite=js` :
  479. `/read.php?tid=${tid}&lite=js`;
  480. fetch(api)
  481. .then((res) => res.blob())
  482. .then((blob) => {
  483. const reader = new FileReader();
  484.  
  485. reader.onload = () => {
  486. const text = reader.result;
  487. // 修复接口数据问题
  488. try {
  489. const result = eval(`
  490. (${text.replace("window.script_muti_get_var_store=", "")})
  491. `);
  492.  
  493. resolve(result.data);
  494. } catch (e) {
  495. resolve({});
  496. }
  497. };
  498. reader.readAsText(blob, "GBK");
  499. })
  500. .catch(() => {
  501. resolve({});
  502. });
  503. });
  504. if (tInfo["__U"]) {
  505. Object.values(tInfo["__U"]).map((item) => {
  506. const uid = item["uid"];
  507.  
  508. if (n.userInfo.users[uid] === undefined) {
  509. n.userInfo.users[uid] = item;
  510. }
  511. });
  512. }
  513.  
  514. if (tInfo["__T"]) {
  515. const uid = tInfo["__T"]["authorid"];
  516.  
  517. const subject = tInfo["__R"]["0"]["subject"];
  518.  
  519. const content = tInfo["__R"]["0"]["content"] + "";
  520. return getFilterMode(uid, subject, content);
  521. }
  522. if (uid) {
  523. return getFilterMode(uid, temp, '');
  524. }
  525. return FILTER_MODE.indexOf("隐藏");
  526. };
  527.  
  528. // 处理引用
  529. const handleQuote = async (content) => {
  530. const quotes = content.querySelectorAll(".quote");
  531.  
  532. await Promise.all(
  533. [...quotes].map(async (quote) => {
  534. const uid = (() => {
  535. const ele = quote.querySelector("a[href^='/nuke.php']");
  536.  
  537. if (ele) {
  538. const res = ele.getAttribute("href").match(/uid=(\S+)/);
  539.  
  540. if (res) {
  541. return res[1];
  542. }
  543. }
  544.  
  545. return 0;
  546. })();
  547.  
  548. const filterMode = await new Promise(async (resolve) => {
  549. const mode = await getFilterMode(uid, "", quote.innerText);
  550.  
  551. if (mode === 0) {
  552. resolve(data.options.filterMode);
  553. }
  554.  
  555. if (mode > 0) {
  556. resolve(FILTER_MODE[mode]);
  557. }
  558.  
  559. resolve("");
  560. });
  561.  
  562. if (filterMode === "标记") {
  563. quote.innerHTML = `
  564. <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
  565. <span class="crimson">Troll must die.</span>
  566. <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
  567. <div style="display: none;" name="troll_${uid}">
  568. ${quote.innerHTML}
  569. </div>
  570. </div>`;
  571. } else if (filterMode === "遮罩") {
  572. const source = document.createElement("DIV");
  573.  
  574. source.innerHTML = quote.innerHTML;
  575. source.style.display = "none";
  576.  
  577. const caption = document.createElement("CAPTION");
  578.  
  579. caption.className = "filter-mask filter-mask-block";
  580.  
  581. caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
  582. caption.onclick = () => {
  583. quote.removeChild(caption);
  584.  
  585. source.style.display = "";
  586. };
  587.  
  588. quote.innerHTML = "";
  589. quote.appendChild(source);
  590. quote.appendChild(caption);
  591. } else if (filterMode === "隐藏") {
  592. quote.innerHTML = "";
  593. }
  594. })
  595. );
  596. };
  597.  
  598. // 过滤
  599. const reFilter = (() => {
  600. let hasNext = false;
  601. let isRunning = false;
  602.  
  603. const func = async () => {
  604. const tPage = location.pathname === "/thread.php";
  605. const pPage = location.pathname === "/read.php";
  606. const uPage = location.pathname === "/nuke.php";
  607.  
  608. if (tPage && new RegExp(`authorid=${self}`).test(location.search)) {
  609. return;
  610. }
  611.  
  612. if (tPage) {
  613. const tData = n.topicArg.data;
  614.  
  615. await Promise.all(
  616. Object.values(tData).map(async (item) => {
  617. if (item.containerC) return;
  618. const uid =
  619. item[2].search.match(/uid=(\S+)/) &&
  620. item[2].search.match(/uid=(\S+)/)[1];
  621. const tid = item[8];
  622. const subject = item[1].innerText;
  623.  
  624. const filterMode = await new Promise(async (resolve) => {
  625. const mode = await getFilterModeByTopic(uid, tid, subject);
  626.  
  627. if (mode === 0) {
  628. resolve(data.options.filterMode);
  629. }
  630.  
  631. if (mode > 0) {
  632. resolve(FILTER_MODE[mode]);
  633. }
  634.  
  635. resolve("");
  636. });
  637.  
  638. item.contentC = item[1];
  639.  
  640. item.contentB = item.contentB || item.contentC.innerHTML;
  641.  
  642. item.containerC =
  643. item.containerC || item.contentC.parentNode.parentNode;
  644.  
  645. item.containerC.style = "";
  646. item.contentC.style = "";
  647. item[1].className = item[1].className.replace(" filter-mask", "");
  648. item[2].className = item[2].className.replace(" filter-mask", "");
  649.  
  650. if (filterMode === "标记") {
  651. item.contentC.style = "text-decoration: line-through;";
  652. } else if (filterMode === "遮罩") {
  653. item[1].className += " filter-mask";
  654. item[2].className += " filter-mask";
  655. } else if (filterMode === "隐藏") {
  656. item.containerC.style = "display: none;";
  657. }
  658. })
  659. );
  660. } else if (pPage) {
  661. const pData = n.postArg.data;
  662.  
  663. await Promise.all(
  664. Object.values(pData).map(async (item) => {
  665. if (~~item.pAid === self) return;
  666. if (item.containerC) return;
  667.  
  668. if (typeof item.i === "number") {
  669. item.actionC =
  670. item.actionC ||
  671. (() => {
  672. const ele = item.uInfoC.querySelector('[name="uid"]');
  673.  
  674. ele.onclick = null;
  675.  
  676. return ele;
  677. })();
  678.  
  679. item.tagC =
  680. item.tagC ||
  681. (() => {
  682. const tc = document.createElement("div");
  683.  
  684. tc.className = "filter-tags";
  685.  
  686. item.uInfoC.appendChild(tc);
  687.  
  688. return tc;
  689. })();
  690. }
  691.  
  692. item.pName =
  693. item.pName ||
  694. item.uInfoC.getElementsByClassName("author")[0].innerText;
  695.  
  696. item.reFilter =
  697. item.reFilter ||
  698. (async () => {
  699. const uid = item.pAid;
  700.  
  701. const filterMode = await new Promise(async (resolve) => {
  702. const mode = await getFilterMode(
  703. uid,
  704. item.subjectC.innerText,
  705. item.contentC.innerText,
  706. );
  707.  
  708. if (mode === 0) {
  709. resolve(data.options.filterMode);
  710. }
  711.  
  712. if (mode > 0) {
  713. resolve(FILTER_MODE[mode]);
  714. }
  715.  
  716. resolve("");
  717. });
  718.  
  719. item.avatarC =
  720. item.avatarC ||
  721. (() => {
  722. const tc = document.createElement("div");
  723.  
  724. const avatar = document.getElementById(
  725. `posteravatar${item.i}`
  726. );
  727.  
  728. if (avatar) {
  729. avatar.parentNode.insertBefore(tc, avatar.nextSibling);
  730.  
  731. tc.appendChild(avatar);
  732. }
  733.  
  734. return tc;
  735. })();
  736.  
  737. item.contentB = item.contentB || item.contentC.innerHTML;
  738.  
  739. item.containerC =
  740. item.containerC ||
  741. (() => {
  742. let temp = item.contentC;
  743.  
  744. if (item.i >= 0) {
  745. while (temp.nodeName !== "TBODY") {
  746. temp = temp.parentNode;
  747. }
  748. } else {
  749. while (temp.nodeName !== "DIV") {
  750. temp = temp.parentNode;
  751. }
  752. }
  753.  
  754. return temp;
  755. })();
  756.  
  757. item.avatarC.style.display = "";
  758. item.containerC.style.display = "";
  759. item.contentC.innerHTML = item.contentB;
  760.  
  761. if (item.actionC) {
  762. item.actionC.style = "background: #aaa;";
  763. }
  764.  
  765. if (filterMode === "标记") {
  766. item.avatarC.style.display = "none";
  767. item.contentC.innerHTML = `
  768. <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
  769. <span class="crimson">Troll must die.</span>
  770. <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
  771. <div style="display: none;" name="troll_${uid}">
  772. ${item.contentB}
  773. </div>
  774. </div>`;
  775.  
  776. if (item.actionC && data.users[uid]) {
  777. item.actionC.style = "background: #cb4042;";
  778. }
  779. } else if (filterMode === "遮罩") {
  780. const caption = document.createElement("CAPTION");
  781.  
  782. if (item.i >= 0) {
  783. caption.className = "filter-mask filter-mask-block";
  784. } else {
  785. caption.className = "filter-mask filter-mask-block left";
  786. caption.style = "width: 47%;";
  787. }
  788.  
  789. caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
  790. caption.onclick = () => {
  791. item.containerC.parentNode.removeChild(caption);
  792. item.containerC.style.display = "";
  793. };
  794.  
  795. item.containerC.parentNode.insertBefore(
  796. caption,
  797. item.containerC
  798. );
  799. item.containerC.style.display = "none";
  800.  
  801. if (item.actionC && data.users[uid]) {
  802. item.actionC.style = "background: #cb4042;";
  803. }
  804. } else if (filterMode === "隐藏") {
  805. item.containerC.style.display = "none";
  806. } else {
  807. await handleQuote(item.contentC);
  808. }
  809.  
  810. if (item.tagC) {
  811. const tags = data.users[uid]
  812. ? data.users[uid].tags.map((tag) => data.tags[tag]) || []
  813. : [];
  814.  
  815. item.tagC.style.display = tags.length ? "" : "none";
  816. item.tagC.innerHTML = tags
  817. .map(
  818. (tag) =>
  819. `<b class="block_txt nobr" style="background:${tag.color}; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>`
  820. )
  821. .join("");
  822. }
  823. });
  824.  
  825. if (item.actionC) {
  826. item.actionC.onclick =
  827. item.actionC.onclick ||
  828. ((e) => {
  829. if (item.pAid < 0) return;
  830.  
  831. const user = data.users[item.pAid];
  832.  
  833. if (e.ctrlKey === false) {
  834. editUser(item.pAid, item.pName, item.reFilter);
  835. } else {
  836. if (user) {
  837. delete data.users[user.id];
  838. } else {
  839. addUser(item.pAid, item.pName);
  840. }
  841.  
  842. saveData();
  843. item.reFilter();
  844. }
  845. });
  846. }
  847.  
  848. await item.reFilter();
  849. })
  850. );
  851. } else if (uPage) {
  852. const container = document.getElementById("ucp_block");
  853.  
  854. if (container.firstChild) {
  855. const uid = container.innerText.match(/用户ID\s*:\s*(\S+)/)[1];
  856.  
  857. const name = container.innerText.match(/用户名\s*:\s*(\S+)/)[1];
  858.  
  859. container.tagC =
  860. container.tagC ||
  861. (() => {
  862. const c = document.createElement("span");
  863.  
  864. c.innerHTML = `
  865. <h2 class="catetitle">:: ${name} 的标记 ::</h2>
  866. <div class="cateblock" style="text-align: left; line-height: 1.8em;">
  867. <div class="contentBlock" style="padding: 5px 10px;">
  868. <span>
  869. <ul class="actions" style="padding: 0px; margin: 0px;">
  870. <li style="padding-right: 5px;">
  871. <span>
  872. <a href="javascript: void(0);">[编辑 ${name} 的标记]</a>
  873. </span>
  874. </li>
  875. <div class="clear"></div>
  876. </ul>
  877. </span>
  878. <div class="filter-tags"></div>
  879. <div class="clear"></div>
  880. </div>
  881. </div>
  882. `;
  883.  
  884. c.getElementsByTagName("a")[0].onclick = () => {
  885. editUser(uid, name, container.refresh);
  886. };
  887.  
  888. container.firstChild.insertBefore(
  889. c,
  890. container.firstChild.childNodes[1]
  891. );
  892.  
  893. return c.getElementsByClassName("filter-tags")[0];
  894. })();
  895.  
  896. container.refresh = () => {
  897. container.tagC.innerHTML = data.users[uid].tags
  898. .map(
  899. (tag) =>
  900. `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`
  901. )
  902. .join("");
  903. };
  904.  
  905. container.refresh();
  906. }
  907. }
  908. };
  909.  
  910. const execute = () =>
  911. func().finally(() => {
  912. if (hasNext) {
  913. hasNext = false;
  914.  
  915. execute();
  916. } else {
  917. isRunning = false;
  918. }
  919. });
  920.  
  921. return async () => {
  922. if (isRunning) {
  923. hasNext = true;
  924. } else {
  925. isRunning = true;
  926.  
  927. await execute();
  928. }
  929. };
  930. })();
  931.  
  932. // STYLE
  933. GM_addStyle(`
  934. .filter-table-wrapper {
  935. max-height: 80vh;
  936. overflow-y: auto;
  937. }
  938. .filter-table {
  939. margin: 0;
  940. }
  941. .filter-table th,
  942. .filter-table td {
  943. position: relative;
  944. white-space: nowrap;
  945. }
  946. .filter-table th {
  947. position: sticky;
  948. top: 2px;
  949. z-index: 1;
  950. }
  951. .filter-table input:not([type]), .filter-table input[type="text"] {
  952. margin: 0;
  953. box-sizing: border-box;
  954. height: 100%;
  955. width: 100%;
  956. }
  957. .filter-input-wrapper {
  958. position: absolute;
  959. top: 6px;
  960. right: 6px;
  961. bottom: 6px;
  962. left: 6px;
  963. }
  964. .filter-text-ellipsis {
  965. display: flex;
  966. }
  967. .filter-text-ellipsis > * {
  968. flex: 1;
  969. width: 1px;
  970. overflow: hidden;
  971. text-overflow: ellipsis;
  972. }
  973. .filter-button-group {
  974. margin: -.1em -.2em;
  975. }
  976. .filter-tags {
  977. margin: 2px -0.2em 0;
  978. text-align: left;
  979. }
  980. .filter-mask {
  981. margin: 1px;
  982. color: #81C7D4;
  983. background: #81C7D4;
  984. }
  985. .filter-mask-block {
  986. display: block;
  987. border: 1px solid #66BAB7;
  988. text-align: center !important;
  989. }
  990. .filter-input-wrapper {
  991. position: absolute;
  992. top: 6px;
  993. right: 6px;
  994. bottom: 6px;
  995. left: 6px;
  996. }
  997. `);
  998.  
  999. // UI
  1000. const u = (() => {
  1001. const modules = {};
  1002.  
  1003. const tabContainer = (() => {
  1004. const c = document.createElement("div");
  1005.  
  1006. c.className = "w100";
  1007. c.innerHTML = `
  1008. <div class="right_" style="margin-bottom: 5px;">
  1009. <table class="stdbtn" cellspacing="0">
  1010. <tbody>
  1011. <tr></tr>
  1012. </tbody>
  1013. </table>
  1014. </div>
  1015. <div class="clear"></div>
  1016. `;
  1017.  
  1018. return c;
  1019. })();
  1020.  
  1021. const tabPanelContainer = (() => {
  1022. const c = document.createElement("div");
  1023.  
  1024. c.style = "width: 80vw;";
  1025.  
  1026. return c;
  1027. })();
  1028.  
  1029. const content = (() => {
  1030. const c = document.createElement("div");
  1031.  
  1032. c.append(tabContainer);
  1033. c.append(tabPanelContainer);
  1034.  
  1035. return c;
  1036. })();
  1037.  
  1038. const addModule = (() => {
  1039. const tc = tabContainer.getElementsByTagName("tr")[0];
  1040. const cc = tabPanelContainer;
  1041.  
  1042. return (module) => {
  1043. const tabBox = document.createElement("td");
  1044.  
  1045. tabBox.innerHTML = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`;
  1046.  
  1047. const tab = tabBox.childNodes[0];
  1048.  
  1049. const toggle = () => {
  1050. Object.values(modules).forEach((item) => {
  1051. if (item.tab === tab) {
  1052. item.tab.className = "nobr";
  1053. item.content.style = "display: block";
  1054. item.refresh();
  1055. } else {
  1056. item.tab.className = "nobr silver";
  1057. item.content.style = "display: none";
  1058. }
  1059. });
  1060. };
  1061.  
  1062. tc.append(tabBox);
  1063. cc.append(module.content);
  1064.  
  1065. tab.onclick = toggle;
  1066.  
  1067. modules[module.name] = {
  1068. ...module,
  1069. tab,
  1070. toggle,
  1071. };
  1072.  
  1073. return modules[module.name];
  1074. };
  1075. })();
  1076.  
  1077. return {
  1078. content,
  1079. modules,
  1080. addModule,
  1081. };
  1082. })();
  1083.  
  1084. // 用户
  1085. const userModule = (() => {
  1086. const content = (() => {
  1087. const c = document.createElement("div");
  1088.  
  1089. c.style = "display: none";
  1090. c.innerHTML = `
  1091. <div class="filter-table-wrapper">
  1092. <table class="filter-table forumbox">
  1093. <thead>
  1094. <tr class="block_txt_c0">
  1095. <th class="c1" width="1">昵称</th>
  1096. <th class="c2">标记</th>
  1097. <th class="c3" width="1">过滤方式</th>
  1098. <th class="c4" width="1">操作</th>
  1099. </tr>
  1100. </thead>
  1101. <tbody></tbody>
  1102. </table>
  1103. </div>
  1104. `;
  1105.  
  1106. return c;
  1107. })();
  1108.  
  1109. const refresh = (() => {
  1110. const container = content.getElementsByTagName("tbody")[0];
  1111.  
  1112. const func = () => {
  1113. container.innerHTML = "";
  1114.  
  1115. Object.values(data.users).forEach((item) => {
  1116. const tc = document.createElement("tr");
  1117.  
  1118. tc.className = `row${
  1119. (container.querySelectorAll("TR").length % 2) + 1
  1120. }`;
  1121.  
  1122. tc.refresh = () => {
  1123. if (data.users[item.id]) {
  1124. tc.innerHTML = `
  1125. <td class="c1">
  1126. <a href="/nuke.php?func=ucp&uid=${
  1127. item.id
  1128. }" class="b nobr">[${
  1129. item.name ? "@" + item.name : "#" + item.id
  1130. }]</a>
  1131. </td>
  1132. <td class="c2">
  1133. ${item.tags
  1134. .map((tag) => {
  1135. if (data.tags[tag]) {
  1136. return `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`;
  1137. }
  1138. })
  1139. .join("")}
  1140. </td>
  1141. <td class="c3">
  1142. <div class="filter-table-button-group">
  1143. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1144. </div>
  1145. </td>
  1146. <td class="c4">
  1147. <div class="filter-table-button-group">
  1148. <button>编辑</button>
  1149. <button>删除</button>
  1150. </div>
  1151. </td>
  1152. `;
  1153.  
  1154. const actions = tc.getElementsByTagName("button");
  1155.  
  1156. actions[0].onclick = () => {
  1157. data.users[item.id].filterMode = switchFilterMode(
  1158. data.users[item.id].filterMode || FILTER_MODE[0]
  1159. );
  1160.  
  1161. actions[0].innerHTML = data.users[item.id].filterMode;
  1162.  
  1163. saveData();
  1164. reFilter();
  1165. };
  1166.  
  1167. actions[1].onclick = () => {
  1168. editUser(item.id, item.name, tc.refresh);
  1169. };
  1170.  
  1171. actions[2].onclick = () => {
  1172. if (confirm("是否确认?")) {
  1173. delete data.users[item.id];
  1174. container.removeChild(tc);
  1175.  
  1176. saveData();
  1177. reFilter();
  1178. }
  1179. };
  1180. } else {
  1181. tc.remove();
  1182. }
  1183. };
  1184.  
  1185. tc.refresh();
  1186.  
  1187. container.appendChild(tc);
  1188. });
  1189. };
  1190.  
  1191. return func;
  1192. })();
  1193.  
  1194. return {
  1195. name: "用户",
  1196. content,
  1197. refresh,
  1198. };
  1199. })();
  1200.  
  1201. // 标记
  1202. const tagModule = (() => {
  1203. const content = (() => {
  1204. const c = document.createElement("div");
  1205.  
  1206. c.style = "display: none";
  1207. c.innerHTML = `
  1208. <div class="filter-table-wrapper">
  1209. <table class="filter-table forumbox">
  1210. <thead>
  1211. <tr class="block_txt_c0">
  1212. <th class="c1" width="1">标记</th>
  1213. <th class="c2">列表</th>
  1214. <th class="c3" width="1">过滤方式</th>
  1215. <th class="c4" width="1">操作</th>
  1216. </tr>
  1217. </thead>
  1218. <tbody></tbody>
  1219. </table>
  1220. </div>
  1221. `;
  1222.  
  1223. return c;
  1224. })();
  1225.  
  1226. const refresh = (() => {
  1227. const container = content.getElementsByTagName("tbody")[0];
  1228.  
  1229. const func = () => {
  1230. container.innerHTML = "";
  1231.  
  1232. Object.values(data.tags).forEach((item) => {
  1233. const tc = document.createElement("tr");
  1234.  
  1235. tc.className = `row${
  1236. (container.querySelectorAll("TR").length % 2) + 1
  1237. }`;
  1238.  
  1239. tc.innerHTML = `
  1240. <td class="c1">
  1241. <b class="block_txt nobr" style="background:${
  1242. item.color
  1243. }; color:#fff; margin: 0.1em 0.2em;">${item.name}</b>
  1244. </td>
  1245. <td class="c2">
  1246. <button>${
  1247. Object.values(data.users).filter((user) =>
  1248. user.tags.find((tag) => tag === item.id)
  1249. ).length
  1250. }
  1251. </button>
  1252. <div style="white-space: normal; display: none;">
  1253. ${Object.values(data.users)
  1254. .filter((user) =>
  1255. user.tags.find((tag) => tag === item.id)
  1256. )
  1257. .map(
  1258. (user) =>
  1259. `<a href="/nuke.php?func=ucp&uid=${
  1260. user.id
  1261. }" class="b nobr">[${
  1262. user.name ? "@" + user.name : "#" + user.id
  1263. }]</a>`
  1264. )
  1265. .join("")}
  1266. </div>
  1267. </td>
  1268. <td class="c3">
  1269. <div class="filter-table-button-group">
  1270. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1271. </div>
  1272. </td>
  1273. <td class="c4">
  1274. <div class="filter-table-button-group">
  1275. <button>删除</button>
  1276. </div>
  1277. </td>
  1278. `;
  1279.  
  1280. const actions = tc.getElementsByTagName("button");
  1281.  
  1282. actions[0].onclick = (() => {
  1283. let hide = true;
  1284. return () => {
  1285. hide = !hide;
  1286. actions[0].nextElementSibling.style.display = hide
  1287. ? "none"
  1288. : "block";
  1289. };
  1290. })();
  1291.  
  1292. actions[1].onclick = () => {
  1293. data.tags[item.id].filterMode = switchFilterMode(
  1294. data.tags[item.id].filterMode || FILTER_MODE[0]
  1295. );
  1296.  
  1297. actions[1].innerHTML = data.tags[item.id].filterMode;
  1298.  
  1299. saveData();
  1300. reFilter();
  1301. };
  1302.  
  1303. actions[2].onclick = () => {
  1304. if (confirm("是否确认?")) {
  1305. delete data.tags[item.id];
  1306.  
  1307. Object.values(data.users).forEach((user) => {
  1308. const index = user.tags.findIndex((tag) => tag === item.id);
  1309. if (index >= 0) {
  1310. user.tags.splice(index, 1);
  1311. }
  1312. });
  1313.  
  1314. container.removeChild(tc);
  1315.  
  1316. saveData();
  1317. reFilter();
  1318. }
  1319. };
  1320.  
  1321. container.appendChild(tc);
  1322. });
  1323. };
  1324.  
  1325. return func;
  1326. })();
  1327.  
  1328. return {
  1329. name: "标记",
  1330. content,
  1331. refresh,
  1332. };
  1333. })();
  1334.  
  1335. // 关键字
  1336. const keywordModule = (() => {
  1337. const content = (() => {
  1338. const c = document.createElement("div");
  1339.  
  1340. c.style = "display: none";
  1341. c.innerHTML = `
  1342. <div class="filter-table-wrapper">
  1343. <table class="filter-table forumbox">
  1344. <thead>
  1345. <tr class="block_txt_c0">
  1346. <th class="c1">列表</th>
  1347. <th class="c2" width="1">过滤方式</th>
  1348. <th class="c3" width="1">包括内容</th>
  1349. <th class="c4" width="1">操作</th>
  1350. </tr>
  1351. </thead>
  1352. <tbody></tbody>
  1353. </table>
  1354. </div>
  1355. <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用&quot;|&quot;隔开,&quot;ABC|DEF&quot;即为屏蔽带有ABC或者DEF的内容。</div>
  1356. `;
  1357.  
  1358. return c;
  1359. })();
  1360.  
  1361. const refresh = (() => {
  1362. const container = content.getElementsByTagName("tbody")[0];
  1363.  
  1364. const func = () => {
  1365. container.innerHTML = "";
  1366.  
  1367. Object.values(data.keywords).forEach((item) => {
  1368. const tc = document.createElement("tr");
  1369.  
  1370. tc.className = `row${
  1371. (container.querySelectorAll("TR").length % 2) + 1
  1372. }`;
  1373.  
  1374. tc.innerHTML = `
  1375. <td class="c1">
  1376. <div class="filter-input-wrapper">
  1377. <input value="${item.keyword || ""}" />
  1378. </div>
  1379. </td>
  1380. <td class="c2">
  1381. <div class="filter-table-button-group">
  1382. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1383. </div>
  1384. </td>
  1385. <td class="c3">
  1386. <div style="text-align: center;">
  1387. <input type="checkbox" ${
  1388. item.filterLevel ? `checked="checked"` : ""
  1389. } />
  1390. </div>
  1391. </td>
  1392. <td class="c4">
  1393. <div class="filter-table-button-group">
  1394. <button>保存</button>
  1395. <button>删除</button>
  1396. </div>
  1397. </td>
  1398. `;
  1399.  
  1400. const inputElement = tc.querySelector("INPUT");
  1401. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1402. const actions = tc.getElementsByTagName("button");
  1403.  
  1404. actions[0].onclick = () => {
  1405. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1406. };
  1407.  
  1408. actions[1].onclick = () => {
  1409. if (inputElement.value) {
  1410. data.keywords[item.id] = {
  1411. id: item.id,
  1412. keyword: inputElement.value,
  1413. filterMode: actions[0].innerHTML,
  1414. filterLevel: levelElement.checked ? 1 : 0,
  1415. };
  1416.  
  1417. saveData();
  1418. refresh();
  1419. }
  1420. };
  1421.  
  1422. actions[2].onclick = () => {
  1423. if (confirm("是否确认?")) {
  1424. delete data.keywords[item.id];
  1425.  
  1426. saveData();
  1427. refresh();
  1428. }
  1429. };
  1430.  
  1431. container.appendChild(tc);
  1432. });
  1433.  
  1434. {
  1435. const tc = document.createElement("tr");
  1436.  
  1437. tc.className = `row${
  1438. (container.querySelectorAll("TR").length % 2) + 1
  1439. }`;
  1440.  
  1441. tc.innerHTML = `
  1442. <td class="c1">
  1443. <div class="filter-input-wrapper">
  1444. <input value="" />
  1445. </div>
  1446. </td>
  1447. <td class="c2">
  1448. <div class="filter-table-button-group">
  1449. <button>${FILTER_MODE[0]}</button>
  1450. </div>
  1451. </td>
  1452. <td class="c3">
  1453. <div style="text-align: center;">
  1454. <input type="checkbox" />
  1455. </div>
  1456. </td>
  1457. <td class="c4">
  1458. <div class="filter-table-button-group">
  1459. <button>添加</button>
  1460. </div>
  1461. </td>
  1462. `;
  1463.  
  1464. const inputElement = tc.querySelector("INPUT");
  1465. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1466. const actions = tc.getElementsByTagName("button");
  1467.  
  1468. actions[0].onclick = () => {
  1469. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1470. };
  1471.  
  1472. actions[1].onclick = () => {
  1473. if (inputElement.value) {
  1474. addKeyword(
  1475. inputElement.value,
  1476. actions[0].innerHTML,
  1477. levelElement.checked ? 1 : 0
  1478. );
  1479.  
  1480. saveData();
  1481. refresh();
  1482. }
  1483. };
  1484.  
  1485. container.appendChild(tc);
  1486. }
  1487. };
  1488.  
  1489. return func;
  1490. })();
  1491.  
  1492. return {
  1493. name: "关键字",
  1494. content,
  1495. refresh,
  1496. };
  1497. })();
  1498.  
  1499. // 通用设置
  1500. const commonModule = (() => {
  1501. const content = (() => {
  1502. const c = document.createElement("div");
  1503.  
  1504. c.style = "display: none";
  1505.  
  1506. return c;
  1507. })();
  1508.  
  1509. const refresh = (() => {
  1510. const container = content;
  1511.  
  1512. const func = () => {
  1513. container.innerHTML = "";
  1514.  
  1515. // 默认过滤方式
  1516. {
  1517. const tc = document.createElement("div");
  1518.  
  1519. tc.innerHTML += `
  1520. <div>默认过滤方式</div>
  1521. <div></div>
  1522. <div class="silver" style="margin-top: 10px;">${FILTER_TIPS}</div>
  1523. `;
  1524.  
  1525. ["标记", "遮罩", "隐藏"].forEach((item, index) => {
  1526. const ele = document.createElement("SPAN");
  1527.  
  1528. ele.innerHTML += `
  1529. <input id="s-fm-${index}" type="radio" name="filterType" ${
  1530. data.options.filterMode === item && "checked"
  1531. }>
  1532. <label for="s-fm-${index}" style="cursor: pointer;">${item}</label>
  1533. `;
  1534.  
  1535. const inp = ele.querySelector("input");
  1536.  
  1537. inp.onchange = () => {
  1538. if (inp.checked) {
  1539. data.options.filterMode = item;
  1540. saveData();
  1541. reFilter();
  1542. }
  1543. };
  1544.  
  1545. tc.querySelectorAll("div")[1].append(ele);
  1546. });
  1547.  
  1548. container.appendChild(tc);
  1549. }
  1550.  
  1551. // 小号过滤(时间)
  1552. {
  1553. const tc = document.createElement("div");
  1554.  
  1555. tc.innerHTML += `
  1556. <br/>
  1557. <div>
  1558. 隐藏注册时间小于<input value="${
  1559. (data.options.filterRegdateLimit || 0) / 86400000
  1560. }" maxLength="4" style="width: 48px;" />天的用户
  1561. <button>确认</button>
  1562. </div>
  1563. `;
  1564.  
  1565. const actions = tc.getElementsByTagName("button");
  1566.  
  1567. actions[0].onclick = () => {
  1568. const v = actions[0].previousElementSibling.value;
  1569.  
  1570. const n = Number(v) || 0;
  1571.  
  1572. data.options.filterRegdateLimit = n < 0 ? 0 : n * 86400000;
  1573.  
  1574. saveData();
  1575. reFilter();
  1576. };
  1577.  
  1578. container.appendChild(tc);
  1579. }
  1580.  
  1581. // 小号过滤(发帖数)
  1582. {
  1583. const tc = document.createElement("div");
  1584.  
  1585. tc.innerHTML += `
  1586. <br/>
  1587. <div>
  1588. 隐藏发帖数量小于<input value="${
  1589. data.options.filterPostnumLimit || 0
  1590. }" maxLength="5" style="width: 48px;" />贴的用户
  1591. <button>确认</button>
  1592. </div>
  1593. `;
  1594.  
  1595. const actions = tc.getElementsByTagName("button");
  1596.  
  1597. actions[0].onclick = () => {
  1598. const v = actions[0].previousElementSibling.value;
  1599.  
  1600. const n = Number(v) || 0;
  1601.  
  1602. data.options.filterPostnumLimit = n < 0 ? 0 : n;
  1603.  
  1604. saveData();
  1605. reFilter();
  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. <button>删除没有标记的用户</button>
  1619. </div>
  1620. `;
  1621.  
  1622. const actions = tc.getElementsByTagName("button");
  1623.  
  1624. actions[0].onclick = () => {
  1625. if (confirm("是否确认?")) {
  1626. Object.values(data.users).forEach((item) => {
  1627. if (item.tags.length === 0) {
  1628. delete data.users[item.id];
  1629. }
  1630. });
  1631.  
  1632. saveData();
  1633. reFilter();
  1634. }
  1635. };
  1636.  
  1637. container.appendChild(tc);
  1638. }
  1639.  
  1640. // 删除没有用户的标记
  1641. {
  1642. const tc = document.createElement("div");
  1643.  
  1644. tc.innerHTML += `
  1645. <br/>
  1646. <div>
  1647. <button>删除没有用户的标记</button>
  1648. </div>
  1649. `;
  1650.  
  1651. const actions = tc.getElementsByTagName("button");
  1652.  
  1653. actions[0].onclick = () => {
  1654. if (confirm("是否确认?")) {
  1655. Object.values(data.tags).forEach((item) => {
  1656. if (
  1657. Object.values(data.users).filter((user) =>
  1658. user.tags.find((tag) => tag === item.id)
  1659. ).length === 0
  1660. ) {
  1661. delete data.tags[item.id];
  1662. }
  1663. });
  1664.  
  1665. saveData();
  1666. reFilter();
  1667. }
  1668. };
  1669.  
  1670. container.appendChild(tc);
  1671. }
  1672. };
  1673.  
  1674. return func;
  1675. })();
  1676.  
  1677. return {
  1678. name: "通用设置",
  1679. content,
  1680. refresh,
  1681. };
  1682. })();
  1683.  
  1684. u.addModule(userModule).toggle();
  1685. u.addModule(tagModule);
  1686. u.addModule(keywordModule);
  1687. u.addModule(commonModule);
  1688.  
  1689. // 增加菜单项
  1690. (() => {
  1691. const title = "过滤设置";
  1692.  
  1693. let window;
  1694.  
  1695. n.mainMenu.addItemOnTheFly(title, null, () => {
  1696. if (window === undefined) {
  1697. window = n.createCommmonWindow();
  1698. }
  1699.  
  1700. window._.addContent(null);
  1701. window._.addTitle(title);
  1702. window._.addContent(u.content);
  1703. window._.show();
  1704. });
  1705. })();
  1706.  
  1707. // 执行过滤
  1708. (() => {
  1709. const hookFunction = (object, functionName, callback) => {
  1710. ((originalFunction) => {
  1711. object[functionName] = function () {
  1712. const returnValue = originalFunction.apply(this, arguments);
  1713.  
  1714. callback.apply(this, [returnValue, originalFunction, arguments]);
  1715.  
  1716. return returnValue;
  1717. };
  1718. })(object[functionName]);
  1719. };
  1720.  
  1721. const initialized = {
  1722. topicArg: false,
  1723. postArg: false,
  1724. };
  1725.  
  1726. hookFunction(n, "eval", () => {
  1727. if (Object.values(initialized).findIndex((item) => item === false) < 0) {
  1728. return;
  1729. }
  1730.  
  1731. if (n.topicArg && initialized.topicArg === false) {
  1732. hookFunction(n.topicArg, "add", reFilter);
  1733.  
  1734. initialized.topicArg = true;
  1735. }
  1736.  
  1737. if (n.postArg && initialized.postArg === false) {
  1738. hookFunction(n.postArg, "proc", reFilter);
  1739.  
  1740. initialized.postArg = true;
  1741. }
  1742. });
  1743.  
  1744. if (n.ucp) {
  1745. hookFunction(n.ucp, "_echo", reFilter);
  1746. }
  1747.  
  1748. reFilter();
  1749. })();
  1750. })(commonui, __CURRENT_UID);