NGA Filter

NGA 屏蔽插件,支持用户、标记、关键字、属地、小号、低声望、匿名过滤。troll must die。

当前为 2023-06-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name NGA Filter
  3. // @namespace https://greasyfork.org/users/263018
  4. // @version 1.10.0
  5. // @author snyssss
  6. // @description NGA 屏蔽插件,支持用户、标记、关键字、属地、小号、低声望、匿名过滤。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. // @grant GM_registerMenuCommand
  17.  
  18. // @noframes
  19. // ==/UserScript==
  20.  
  21. ((n, self) => {
  22. if (n === undefined) return;
  23.  
  24. // KEY
  25. const DATA_KEY = "NGAFilter";
  26. const USER_AGENT_KEY = "USER_AGENT_KEY";
  27.  
  28. // User Agent
  29. const USER_AGENT = (() => {
  30. const data = GM_getValue(USER_AGENT_KEY) || "Nga_Official";
  31.  
  32. GM_registerMenuCommand(`修改UA${data}`, () => {
  33. const value = prompt("修改UA", data);
  34.  
  35. if (value) {
  36. GM_setValue(USER_AGENT_KEY, value);
  37.  
  38. location.reload();
  39. }
  40. });
  41.  
  42. return data;
  43. })();
  44.  
  45. // 简单的统一请求
  46. const request = (url, config = {}) =>
  47. fetch(url, {
  48. headers: {
  49. "X-User-Agent": USER_AGENT,
  50. },
  51. ...config,
  52. });
  53.  
  54. // 过滤提示
  55. const FILTER_TIPS =
  56. "过滤顺序:用户 &gt; 标记 &gt; 关键字 &gt; 属地<br/>过滤级别:隐藏 &gt; 遮罩 &gt; 标记 &gt; 继承 &gt; 显示<br/>相同类型按最高级别过滤";
  57.  
  58. // 过滤方式
  59. const FILTER_MODE = ["继承", "标记", "遮罩", "隐藏", "显示"];
  60.  
  61. // 切换过滤方式
  62. const switchFilterMode = (value) => {
  63. const next = FILTER_MODE.indexOf(value) + 1;
  64.  
  65. if (next >= FILTER_MODE.length) {
  66. return FILTER_MODE[0];
  67. }
  68.  
  69. return FILTER_MODE[next];
  70. };
  71.  
  72. // 数据
  73. const data = (() => {
  74. const d = {
  75. tags: {},
  76. users: {},
  77. keywords: {},
  78. locations: {},
  79. options: {
  80. filterRegdateLimit: 0,
  81. filterPostnumLimit: 0,
  82. filterReputationLimit: NaN,
  83. filterAnony: false,
  84. filterMode: "隐藏",
  85. },
  86. };
  87.  
  88. const v = GM_getValue(DATA_KEY);
  89.  
  90. if (typeof v !== "object") {
  91. return d;
  92. }
  93.  
  94. return Object.assign(d, v);
  95. })();
  96.  
  97. // 保存数据
  98. const saveData = () => {
  99. GM_setValue(DATA_KEY, data);
  100. };
  101.  
  102. // 增加标记
  103. const addTag = (name) => {
  104. const tag = Object.values(data.tags).find((item) => item.name === name);
  105.  
  106. if (tag) return tag.id;
  107.  
  108. const id =
  109. Math.max(...Object.values(data.tags).map((item) => item.id), 0) + 1;
  110.  
  111. const hash = (() => {
  112. let h = 5381;
  113. for (var i = 0; i < name.length; i++) {
  114. h = ((h << 5) + h + name.charCodeAt(i)) & 0xffffffff;
  115. }
  116. return h;
  117. })();
  118.  
  119. const hex = Math.abs(hash).toString(16) + "000000";
  120.  
  121. const hsv = [
  122. `0x${hex.substring(2, 4)}` / 255,
  123. `0x${hex.substring(2, 4)}` / 255 / 2 + 0.25,
  124. `0x${hex.substring(4, 6)}` / 255 / 2 + 0.25,
  125. ];
  126.  
  127. const rgb = n.hsvToRgb(hsv[0], hsv[1], hsv[2]);
  128.  
  129. const color = ["#", ...rgb].reduce((a, b) => {
  130. return a + ("0" + b.toString(16)).slice(-2);
  131. });
  132.  
  133. data.tags[id] = {
  134. id,
  135. name,
  136. color,
  137. filterMode: FILTER_MODE[0],
  138. };
  139.  
  140. saveData();
  141.  
  142. return id;
  143. };
  144.  
  145. // 增加用户
  146. const addUser = (id, name = null, tags = [], filterMode = FILTER_MODE[0]) => {
  147. if (data.users[id]) return data.users[id];
  148.  
  149. data.users[id] = {
  150. id,
  151. name,
  152. tags,
  153. filterMode,
  154. };
  155.  
  156. saveData();
  157.  
  158. return data.users[id];
  159. };
  160.  
  161. // 增加关键字
  162. const addKeyword = (
  163. keyword,
  164. filterMode = FILTER_MODE[0],
  165. filterLevel = 0
  166. ) => {
  167. const id =
  168. Math.max(...Object.values(data.keywords).map((item) => item.id), 0) + 1;
  169.  
  170. data.keywords[id] = {
  171. id,
  172. keyword,
  173. filterMode,
  174. filterLevel,
  175. };
  176.  
  177. saveData();
  178.  
  179. return id;
  180. };
  181.  
  182. // 增加属地
  183. const addLocation = (keyword, filterMode = FILTER_MODE[0]) => {
  184. const id =
  185. Math.max(...Object.values(data.locations).map((item) => item.id), 0) + 1;
  186.  
  187. data.locations[id] = {
  188. id,
  189. keyword,
  190. filterMode,
  191. };
  192.  
  193. saveData();
  194.  
  195. return id;
  196. };
  197.  
  198. // 旧版本数据迁移
  199. {
  200. const dataKey = "troll_data";
  201. const modeKey = "troll_mode";
  202. const keywordKey = "troll_keyword";
  203.  
  204. if (localStorage.getItem(dataKey)) {
  205. let trollMap = (function () {
  206. try {
  207. return JSON.parse(localStorage.getItem(dataKey)) || {};
  208. } catch (e) {}
  209.  
  210. return {};
  211. })();
  212.  
  213. let filterMode = ~~localStorage.getItem(modeKey);
  214.  
  215. let filterKeyword = localStorage.getItem(keywordKey) || "";
  216.  
  217. // 整理标签
  218. [...new Set(Object.values(trollMap).flat())].forEach((item) =>
  219. addTag(item)
  220. );
  221.  
  222. // 整理用户
  223. Object.keys(trollMap).forEach((item) => {
  224. addUser(
  225. item,
  226. null,
  227. (typeof trollMap[item] === "object" ? trollMap[item] : []).map(
  228. (tag) => addTag(tag)
  229. )
  230. );
  231. });
  232.  
  233. data.options.filterMode = filterMode ? "隐藏" : "标记";
  234. data.options.keyword = filterKeyword;
  235.  
  236. localStorage.removeItem(dataKey);
  237. localStorage.removeItem(modeKey);
  238. localStorage.removeItem(keywordKey);
  239.  
  240. saveData();
  241. }
  242.  
  243. // v1.1.0 -> v1.1.1
  244. {
  245. Object.values(data.users).forEach(({ id, name, tags, enabled }) => {
  246. if (enabled !== undefined) {
  247. data.users[id] = {
  248. id,
  249. name,
  250. tags,
  251. filterMode: enabled ? "继承" : "显示",
  252. };
  253. }
  254. });
  255.  
  256. Object.values(data.tags).forEach(({ id, name, color, enabled }) => {
  257. if (enabled !== undefined) {
  258. data.tags[id] = {
  259. id,
  260. name,
  261. color,
  262. filterMode: enabled ? "继承" : "显示",
  263. };
  264. }
  265. });
  266.  
  267. if (data.options.filterMode === 0) {
  268. data.options.filterMode = "隐藏";
  269. } else if (data.options.filterMode === 1) {
  270. data.options.filterMode = "标记";
  271. }
  272.  
  273. saveData();
  274. }
  275.  
  276. // v1.2.x -> v1.3.0
  277. {
  278. if (data.options.keyword) {
  279. addKeyword(data.options.keyword);
  280.  
  281. delete data.options.keyword;
  282.  
  283. saveData();
  284. }
  285. }
  286. }
  287.  
  288. // 编辑用户标记
  289. const editUser = (() => {
  290. let window;
  291. return (uid, name, callback) => {
  292. if (window === undefined) {
  293. window = n.createCommmonWindow();
  294. }
  295.  
  296. const user = data.users[uid];
  297.  
  298. const content = document.createElement("div");
  299.  
  300. const size = Math.floor((screen.width * 0.8) / 200);
  301.  
  302. const items = Object.values(data.tags).map(
  303. (tag, index) => `
  304. <td class="c1">
  305. <label for="s-tag-${index}" style="display: block; cursor: pointer;">
  306. <b class="block_txt nobr" style="background:${
  307. tag.color
  308. }; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>
  309. </label>
  310. </td>
  311. <td class="c2" width="1">
  312. <input id="s-tag-${index}" type="checkbox" value="${tag.id}" ${
  313. user && user.tags.find((item) => item === tag.id) && "checked"
  314. }/>
  315. </td>
  316. `
  317. );
  318.  
  319. const rows = [...new Array(Math.ceil(items.length / size))].map(
  320. (item, index) =>
  321. `
  322. <tr class="row${(index % 2) + 1}">
  323. ${items.slice(size * index, size * (index + 1)).join("")}
  324. </tr>
  325. `
  326. );
  327.  
  328. content.className = "w100";
  329. content.innerHTML = `
  330. <div class="filter-table-wrapper" style="width: 80vw;">
  331. <table class="filter-table forumbox">
  332. <tbody>
  333. ${rows.join("")}
  334. </tbody>
  335. </table>
  336. </div>
  337. <div style="margin: 10px 0;">
  338. <input placeholder="一次性添加多个标记用&quot;|&quot;隔开,不会添加重名标记" style="width: -webkit-fill-available;" />
  339. </div>
  340. <div style="margin: 10px 0;">
  341. <span>过滤方式:</span>
  342. <button>${(user && user.filterMode) || FILTER_MODE[0]}</button>
  343. <div class="right_">
  344. <button>删除</button>
  345. <button>保存</button>
  346. </div>
  347. </div>
  348. <div class="silver" style="margin-top: 5px;">${FILTER_TIPS}</div>
  349. `;
  350.  
  351. const actions = content.getElementsByTagName("button");
  352.  
  353. actions[0].onclick = () => {
  354. actions[0].innerText = switchFilterMode(
  355. actions[0].innerText || FILTER_MODE[0]
  356. );
  357. };
  358.  
  359. actions[1].onclick = () => {
  360. if (confirm("是否确认?")) {
  361. delete data.users[uid];
  362.  
  363. saveData();
  364.  
  365. callback && callback();
  366.  
  367. window._.hide();
  368. }
  369. };
  370.  
  371. actions[2].onclick = () => {
  372. if (confirm("是否确认?")) {
  373. const values = [...content.getElementsByTagName("input")];
  374. const newTags = values[values.length - 1].value
  375. .split("|")
  376. .filter((item) => item.length)
  377. .map((item) => addTag(item));
  378. const tags = [
  379. ...new Set(
  380. values
  381. .filter((item) => item.type === "checkbox" && item.checked)
  382. .map((item) => ~~item.value)
  383. .concat(newTags)
  384. ),
  385. ].sort();
  386.  
  387. if (user) {
  388. user.tags = tags;
  389. user.filterMode = actions[0].innerText;
  390. } else {
  391. addUser(uid, name, tags, actions[0].innerText);
  392. }
  393.  
  394. saveData();
  395.  
  396. callback && callback();
  397.  
  398. window._.hide();
  399. }
  400. };
  401.  
  402. if (user === undefined) {
  403. actions[1].style = "display: none;";
  404. }
  405.  
  406. window._.addContent(null);
  407. window._.addTitle(`编辑标记 - ${name ? name : "#" + uid}`);
  408. window._.addContent(content);
  409. window._.show();
  410. };
  411. })();
  412.  
  413. // 猎巫
  414. const witchHunter = (() => {
  415. const key = "WITCH_HUNTER";
  416.  
  417. const data = GM_getValue(key) || {};
  418.  
  419. const add = async (fid, label) => {
  420. if (Object.values(data).find((item) => item.fid === fid)) {
  421. alert("已有相同版面ID");
  422. return;
  423. }
  424.  
  425. const info = await new Promise((resolve) => {
  426. request(`/thread.php?lite=js&fid=${fid}`)
  427. .then((res) => res.blob())
  428. .then((blob) => {
  429. const reader = new FileReader();
  430.  
  431. reader.onload = () => {
  432. const text = reader.result;
  433. const result = JSON.parse(
  434. text.replace("window.script_muti_get_var_store=", "")
  435. );
  436.  
  437. resolve(result.data);
  438. };
  439.  
  440. reader.readAsText(blob, "GBK");
  441. })
  442. .catch(() => {
  443. resolve({});
  444. });
  445. });
  446.  
  447. if (info.__F === undefined) {
  448. alert("版面ID有误");
  449. return;
  450. }
  451.  
  452. const name = info.__F.name;
  453.  
  454. const id = Math.max(...Object.values(data).map((item) => item.id), 0) + 1;
  455.  
  456. const hash = (() => {
  457. let h = 5381;
  458. for (var i = 0; i < label.length; i++) {
  459. h = ((h << 5) + h + label.charCodeAt(i)) & 0xffffffff;
  460. }
  461. return h;
  462. })();
  463.  
  464. const hex = Math.abs(hash).toString(16) + "000000";
  465.  
  466. const hsv = [
  467. `0x${hex.substring(2, 4)}` / 255,
  468. `0x${hex.substring(2, 4)}` / 255 / 2 + 0.25,
  469. `0x${hex.substring(4, 6)}` / 255 / 2 + 0.25,
  470. ];
  471.  
  472. const rgb = n.hsvToRgb(hsv[0], hsv[1], hsv[2]);
  473.  
  474. const color = ["#", ...rgb].reduce((a, b) => {
  475. return a + ("0" + b.toString(16)).slice(-2);
  476. });
  477.  
  478. data[id] = {
  479. id,
  480. fid,
  481. name,
  482. label,
  483. color,
  484. };
  485.  
  486. GM_setValue(key, data);
  487. };
  488.  
  489. const remove = (id) => {
  490. delete data[id];
  491.  
  492. GM_setValue(key, data);
  493. };
  494.  
  495. const run = (uid, element) => {
  496. if (uid < 0) {
  497. return;
  498. }
  499.  
  500. Promise.all(
  501. Object.values(data).map(async (item) => {
  502. const api = `/thread.php?lite=js&fid=${item.fid}&authorid=${uid}`;
  503.  
  504. const verify =
  505. (await new Promise((resolve) => {
  506. request(api)
  507. .then((res) => res.blob())
  508. .then((blob) => {
  509. const reader = new FileReader();
  510.  
  511. reader.onload = () => {
  512. const text = reader.result;
  513. const result = JSON.parse(
  514. text.replace("window.script_muti_get_var_store=", "")
  515. );
  516.  
  517. if (result.error) {
  518. resolve(false);
  519. return;
  520. }
  521.  
  522. resolve(true);
  523. };
  524.  
  525. reader.readAsText(blob, "GBK");
  526. })
  527. .catch(() => {
  528. resolve(false);
  529. });
  530. })) ||
  531. (await new Promise((resolve) => {
  532. request(`${api}&searchpost=1`)
  533. .then((res) => res.blob())
  534. .then((blob) => {
  535. const reader = new FileReader();
  536.  
  537. reader.onload = () => {
  538. const text = reader.result;
  539. const result = JSON.parse(
  540. text.replace("window.script_muti_get_var_store=", "")
  541. );
  542.  
  543. if (result.error) {
  544. resolve(false);
  545. return;
  546. }
  547.  
  548. resolve(true);
  549. };
  550.  
  551. reader.readAsText(blob, "GBK");
  552. })
  553. .catch(() => {
  554. resolve(false);
  555. });
  556. }));
  557.  
  558. if (verify) {
  559. return item;
  560. }
  561. })
  562. )
  563. .then((res) => res.filter((item) => item))
  564. .then((res) => {
  565. res
  566. .filter(
  567. (current, index) =>
  568. res.findIndex((item) => item.label === current.label) === index
  569. )
  570. .forEach((item) => {
  571. element.style.display = "block";
  572. element.innerHTML += `<b class="block_txt nobr" style="background:${item.color}; color:#fff; margin: 0.1em 0.2em;">${item.label}</b>`;
  573. });
  574. });
  575. };
  576.  
  577. return {
  578. add,
  579. remove,
  580. run,
  581. data,
  582. };
  583. })();
  584.  
  585. // 小号过滤和声望过滤
  586. const getFilterModeByUserInfo = async (userInfo, reputation) => {
  587. const filterRegdateLimit = data.options.filterRegdateLimit || 0;
  588.  
  589. const filterPostnumLimit = data.options.filterPostnumLimit || 0;
  590.  
  591. const filterReputationLimit = data.options.filterReputationLimit || NaN;
  592.  
  593. if (userInfo) {
  594. const { regdate, postnum } = userInfo;
  595.  
  596. if (
  597. filterRegdateLimit > 0 &&
  598. regdate * 1000 > new Date() - filterRegdateLimit
  599. ) {
  600. return true;
  601. }
  602.  
  603. if (filterPostnumLimit > 0 && postnum < filterPostnumLimit) {
  604. return true;
  605. }
  606. }
  607.  
  608. if (Number.isNaN(filterReputationLimit) === false) {
  609. if (reputation < filterReputationLimit) {
  610. return true;
  611. }
  612. }
  613.  
  614. return false;
  615. };
  616.  
  617. // 判断过滤方式
  618. const getFilterMode = async (uid, subject, content) => {
  619. let result = -1;
  620.  
  621. const user = data.users[uid];
  622.  
  623. const tags = user ? user.tags.map((tag) => data.tags[tag]) : [];
  624.  
  625. const keywords = Object.values(data.keywords);
  626.  
  627. const locations = Object.values(data.locations);
  628.  
  629. if (uid > 0) {
  630. const userInfo = n.userInfo.users[uid];
  631.  
  632. const reputation = (() => {
  633. const reputations = n.userInfo.reputations;
  634.  
  635. if (reputations) {
  636. for (let fid in reputations) {
  637. return reputations[fid][uid] || 0;
  638. }
  639. }
  640.  
  641. return NaN;
  642. })();
  643.  
  644. if (await getFilterModeByUserInfo(userInfo, reputation)) {
  645. return FILTER_MODE.indexOf("隐藏");
  646. }
  647. } else if (uid < 0 && data.options.filterAnony) {
  648. return FILTER_MODE.indexOf("隐藏");
  649. }
  650.  
  651. if (user) {
  652. const filterMode = FILTER_MODE.indexOf(user.filterMode);
  653.  
  654. if (filterMode > 0) {
  655. return filterMode;
  656. }
  657.  
  658. result = filterMode;
  659. }
  660.  
  661. if (tags.length) {
  662. const filterMode = (() => {
  663. if (tags.some((tag) => tag.filterMode !== "显示")) {
  664. return tags
  665. .filter((tag) => tag.filterMode !== "显示")
  666. .map((tag) => FILTER_MODE.indexOf(tag.filterMode) || 0)
  667. .sort((a, b) => b - a)[0];
  668. }
  669.  
  670. return FILTER_MODE.indexOf("显示");
  671. })();
  672.  
  673. if (filterMode > 0) {
  674. return filterMode;
  675. }
  676.  
  677. result = filterMode;
  678. }
  679.  
  680. if (keywords.length) {
  681. const filterMode = (() => {
  682. const sR = (() => {
  683. if (subject) {
  684. const r = keywords
  685. .filter((item) => item.keyword && item.filterMode !== "显示")
  686. .filter((item) => (item.filterLevel || 0) >= 0)
  687. .sort(
  688. (a, b) =>
  689. FILTER_MODE.indexOf(b.filterMode) -
  690. FILTER_MODE.indexOf(a.filterMode)
  691. )
  692. .find((item) => subject.search(item.keyword) >= 0);
  693.  
  694. if (r) {
  695. return FILTER_MODE.indexOf(r.filterMode);
  696. }
  697. }
  698.  
  699. return -1;
  700. })();
  701.  
  702. const cR = (() => {
  703. if (content) {
  704. const r = keywords
  705. .filter((item) => item.keyword && item.filterMode !== "显示")
  706. .filter((item) => (item.filterLevel || 0) >= 1)
  707. .sort(
  708. (a, b) =>
  709. FILTER_MODE.indexOf(b.filterMode) -
  710. FILTER_MODE.indexOf(a.filterMode)
  711. )
  712. .find((item) => content.search(item.keyword) >= 0);
  713.  
  714. if (r) {
  715. return FILTER_MODE.indexOf(r.filterMode);
  716. }
  717. }
  718.  
  719. return -1;
  720. })();
  721.  
  722. return Math.max(sR, cR, result);
  723. })();
  724.  
  725. if (filterMode > 0) {
  726. return filterMode;
  727. }
  728.  
  729. result = filterMode;
  730. }
  731.  
  732. if (locations.length) {
  733. const { ipLoc } = await new Promise((resolve) => {
  734. request(`/nuke.php?lite=js&__lib=ucp&__act=get&uid=${uid}`)
  735. .then((res) => res.blob())
  736. .then((blob) => {
  737. const reader = new FileReader();
  738.  
  739. reader.onload = () => {
  740. const text = reader.result;
  741. const result = JSON.parse(
  742. text.replace("window.script_muti_get_var_store=", "")
  743. );
  744.  
  745. resolve(result.data[0]);
  746. };
  747.  
  748. reader.readAsText(blob, "GBK");
  749. })
  750. .catch(() => {
  751. resolve({});
  752. });
  753. });
  754.  
  755. if (ipLoc) {
  756. const filterMode = (() => {
  757. const r = locations
  758. .filter((item) => item.keyword && item.filterMode !== "显示")
  759. .sort(
  760. (a, b) =>
  761. FILTER_MODE.indexOf(b.filterMode) -
  762. FILTER_MODE.indexOf(a.filterMode)
  763. )
  764. .find((item) => ipLoc.search(item.keyword) >= 0);
  765.  
  766. if (r) {
  767. return FILTER_MODE.indexOf(r.filterMode);
  768. }
  769.  
  770. return Math.max(r, result);
  771. })();
  772.  
  773. if (filterMode > 0) {
  774. return filterMode;
  775. }
  776.  
  777. result = filterMode;
  778. }
  779. }
  780.  
  781. return result;
  782. };
  783.  
  784. // 根据 TID 获取过滤方式
  785. const getFilterModeByTopic = async (tid) => {
  786. return await new Promise((resolve, reject) => {
  787. const api = `/read.php?tid=${tid}`;
  788.  
  789. request(api)
  790. .then((res) => res.blob())
  791. .then((blob) => {
  792. const getLastIndex = (content, position) => {
  793. if (position >= 0) {
  794. let nextIndex = position + 1;
  795.  
  796. while (nextIndex < content.length) {
  797. if (content[nextIndex] === "}") {
  798. return nextIndex;
  799. }
  800.  
  801. if (content[nextIndex] === "{") {
  802. nextIndex = getLastIndex(content, nextIndex);
  803.  
  804. if (nextIndex < 0) {
  805. break;
  806. }
  807. }
  808.  
  809. nextIndex = nextIndex + 1;
  810. }
  811. }
  812.  
  813. return -1;
  814. };
  815.  
  816. const reader = new FileReader();
  817.  
  818. reader.onload = async () => {
  819. const parser = new DOMParser();
  820.  
  821. const doc = parser.parseFromString(reader.result, "text/html");
  822.  
  823. const html = doc.body.innerHTML;
  824.  
  825. // 验证帖子正常
  826. const verify = doc.querySelector("#m_posts");
  827.  
  828. if (verify) {
  829. // 取得顶楼 UID
  830. const uid = (() => {
  831. const ele = doc.querySelector("#postauthor0");
  832.  
  833. if (ele) {
  834. const res = ele.getAttribute("href").match(/uid=(\S+)/);
  835.  
  836. if (res) {
  837. return res[1];
  838. }
  839. }
  840.  
  841. return 0;
  842. })();
  843.  
  844. // 取得顶楼标题
  845. const subject = doc.querySelector("#postsubject0").innerHTML;
  846.  
  847. // 取得顶楼内容
  848. const content = doc.querySelector("#postcontent0").innerHTML;
  849.  
  850. if (uid && uid > 0) {
  851. // 取得用户信息
  852. const userInfo = (() => {
  853. // 起始JSON
  854. const str = `"${uid}":{`;
  855.  
  856. // 起始下标
  857. const index = html.indexOf(str) + str.length;
  858.  
  859. // 结尾下标
  860. const lastIndex = getLastIndex(html, index);
  861.  
  862. if (lastIndex >= 0) {
  863. try {
  864. return JSON.parse(
  865. `{${html.substring(index, lastIndex)}}`
  866. );
  867. } catch {}
  868. }
  869.  
  870. return null;
  871. })();
  872.  
  873. // 取得用户声望
  874. const reputation = (() => {
  875. const reputations = (() => {
  876. // 起始JSON
  877. const str = `"__REPUTATIONS":{`;
  878.  
  879. // 起始下标
  880. const index = html.indexOf(str) + str.length;
  881.  
  882. // 结尾下标
  883. const lastIndex = getLastIndex(html, index);
  884.  
  885. if (lastIndex >= 0) {
  886. return JSON.parse(
  887. `{${html.substring(index, lastIndex)}}`
  888. );
  889. }
  890.  
  891. return null;
  892. })();
  893.  
  894. if (reputations) {
  895. for (let fid in reputations) {
  896. return reputations[fid][uid] || 0;
  897. }
  898. }
  899.  
  900. return NaN;
  901. })();
  902.  
  903. if (await getFilterModeByUserInfo(userInfo, reputation)) {
  904. resolve(FILTER_MODE.indexOf("隐藏"));
  905. }
  906. }
  907.  
  908. resolve(getFilterMode(uid, subject, content));
  909. } else {
  910. reject();
  911. }
  912. };
  913.  
  914. reader.readAsText(blob, "GBK");
  915. })
  916. .catch(() => {
  917. reject();
  918. });
  919. }).catch(() => {
  920. return FILTER_MODE.indexOf("隐藏");
  921. });
  922. };
  923.  
  924. // 处理引用
  925. const handleQuote = async (content) => {
  926. const quotes = content.querySelectorAll(".quote");
  927.  
  928. await Promise.all(
  929. [...quotes].map(async (quote) => {
  930. const uid = (() => {
  931. const ele = quote.querySelector("a[href^='/nuke.php']");
  932.  
  933. if (ele) {
  934. const res = ele.getAttribute("href").match(/uid=(\S+)/);
  935.  
  936. if (res) {
  937. return res[1];
  938. }
  939. }
  940.  
  941. return 0;
  942. })();
  943.  
  944. const filterMode = await new Promise(async (resolve) => {
  945. const mode = await getFilterMode(uid, "", quote.innerText);
  946.  
  947. if (mode === 0) {
  948. resolve(data.options.filterMode);
  949. }
  950.  
  951. if (mode > 0) {
  952. resolve(FILTER_MODE[mode]);
  953. }
  954.  
  955. resolve("");
  956. });
  957.  
  958. if (filterMode === "标记") {
  959. quote.innerHTML = `
  960. <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
  961. <span class="crimson">Troll must die.</span>
  962. <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
  963. <div style="display: none;" name="troll_${uid}">
  964. ${quote.innerHTML}
  965. </div>
  966. </div>`;
  967. } else if (filterMode === "遮罩") {
  968. const source = document.createElement("DIV");
  969.  
  970. source.innerHTML = quote.innerHTML;
  971. source.style.display = "none";
  972.  
  973. const caption = document.createElement("CAPTION");
  974.  
  975. caption.className = "filter-mask filter-mask-block";
  976.  
  977. caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
  978. caption.onclick = () => {
  979. quote.removeChild(caption);
  980.  
  981. source.style.display = "";
  982. };
  983.  
  984. quote.innerHTML = "";
  985. quote.appendChild(source);
  986. quote.appendChild(caption);
  987. } else if (filterMode === "隐藏") {
  988. quote.innerHTML = "";
  989. }
  990. })
  991. );
  992. };
  993.  
  994. // 过滤
  995. const reFilter = (() => {
  996. let hasNext = false;
  997. let isRunning = false;
  998.  
  999. const func = async () => {
  1000. const tPage = location.pathname === "/thread.php";
  1001. const pPage = location.pathname === "/read.php";
  1002.  
  1003. if (tPage) {
  1004. const params = new URLSearchParams(location.search);
  1005.  
  1006. if (params.has("favor")) {
  1007. return;
  1008. }
  1009.  
  1010. if (params.has("authorid")) {
  1011. return;
  1012. }
  1013. }
  1014.  
  1015. if (tPage) {
  1016. const tData = n.topicArg.data;
  1017.  
  1018. await Promise.all(
  1019. Object.values(tData).map(async (item) => {
  1020. if (item.containerC) return;
  1021.  
  1022. const tid = item[8];
  1023.  
  1024. const filterMode = await new Promise(async (resolve) => {
  1025. const mode = await getFilterModeByTopic(tid);
  1026.  
  1027. if (mode === 0) {
  1028. resolve(data.options.filterMode);
  1029. }
  1030.  
  1031. if (mode > 0) {
  1032. resolve(FILTER_MODE[mode]);
  1033. }
  1034.  
  1035. resolve("");
  1036. });
  1037.  
  1038. item.contentC = item[1];
  1039.  
  1040. item.contentB = item.contentB || item.contentC.innerHTML;
  1041.  
  1042. item.containerC =
  1043. item.containerC || item.contentC.parentNode.parentNode;
  1044.  
  1045. item.containerC.style = "";
  1046. item.contentC.style = "";
  1047. item[1].className = item[1].className.replace(" filter-mask", "");
  1048. item[2].className = item[2].className.replace(" filter-mask", "");
  1049.  
  1050. if (filterMode === "标记") {
  1051. item.contentC.style = "text-decoration: line-through;";
  1052. } else if (filterMode === "遮罩") {
  1053. item[1].className += " filter-mask";
  1054. item[2].className += " filter-mask";
  1055. } else if (filterMode === "隐藏") {
  1056. item.containerC.style = "display: none;";
  1057. }
  1058. })
  1059. );
  1060. } else if (pPage) {
  1061. const pData = n.postArg.data;
  1062.  
  1063. await Promise.all(
  1064. Object.values(pData).map(async (item) => {
  1065. const uid = ~~item.pAid;
  1066.  
  1067. if (uid === self) return;
  1068. if (item.containerC) return;
  1069.  
  1070. if (typeof item.i === "number") {
  1071. item.actionC =
  1072. item.actionC ||
  1073. (() => {
  1074. const container =
  1075. item.uInfoC
  1076. .closest("tr")
  1077. .querySelector(".posterInfoLine") ||
  1078. item.uInfoC.querySelector("div");
  1079.  
  1080. const ele = container.querySelector('[name="uid"]');
  1081.  
  1082. if (ele) {
  1083. ele.innerHTML = `屏蔽`;
  1084. ele.onclick = null;
  1085.  
  1086. return ele;
  1087. }
  1088.  
  1089. const anchor = container.querySelector(".author ~ br");
  1090.  
  1091. if (anchor) {
  1092. const btn = document.createElement("A");
  1093.  
  1094. btn.name = "uid";
  1095. btn.href = "javascript:void(0)";
  1096. btn.className =
  1097. "small_colored_text_btn stxt block_txt_c0 vertmod";
  1098. btn.innerHTML = `屏蔽`;
  1099.  
  1100. anchor.parentNode.insertBefore(btn, anchor);
  1101.  
  1102. return btn;
  1103. }
  1104. })();
  1105.  
  1106. item.tagC =
  1107. item.tagC ||
  1108. (() => {
  1109. const container =
  1110. item.uInfoC
  1111. .closest("tr")
  1112. .querySelector(".posterInfoLine") || item.uInfoC;
  1113.  
  1114. const tc = document.createElement("div");
  1115.  
  1116. tc.className = "filter-tags";
  1117.  
  1118. container.appendChild(tc);
  1119.  
  1120. return tc;
  1121. })();
  1122. }
  1123.  
  1124. item.pName =
  1125. item.pName ||
  1126. (() => {
  1127. const container =
  1128. item.uInfoC.closest("tr").querySelector(".posterInfoLine") ||
  1129. item.uInfoC;
  1130.  
  1131. return container.querySelector(".author").innerText;
  1132. })();
  1133.  
  1134. item.reFilter =
  1135. item.reFilter ||
  1136. (async () => {
  1137. const filterMode = await new Promise(async (resolve) => {
  1138. const mode = await getFilterMode(
  1139. uid,
  1140. item.subjectC.innerText,
  1141. item.contentC.innerText
  1142. );
  1143.  
  1144. if (mode === 0) {
  1145. resolve(data.options.filterMode);
  1146. }
  1147.  
  1148. if (mode > 0) {
  1149. resolve(FILTER_MODE[mode]);
  1150. }
  1151.  
  1152. resolve("");
  1153. });
  1154.  
  1155. item.avatarC =
  1156. item.avatarC ||
  1157. (() => {
  1158. const tc = document.createElement("div");
  1159.  
  1160. const avatar = document.getElementById(
  1161. `posteravatar${item.i}`
  1162. );
  1163.  
  1164. if (avatar) {
  1165. avatar.parentNode.insertBefore(tc, avatar.nextSibling);
  1166.  
  1167. tc.appendChild(avatar);
  1168. }
  1169.  
  1170. return tc;
  1171. })();
  1172.  
  1173. item.contentB = item.contentB || item.contentC.innerHTML;
  1174.  
  1175. item.containerC =
  1176. item.containerC ||
  1177. (() => {
  1178. let temp = item.contentC;
  1179.  
  1180. if (item.i >= 0) {
  1181. while (temp.nodeName !== "TBODY") {
  1182. temp = temp.parentNode;
  1183. }
  1184. } else {
  1185. while (temp.nodeName !== "DIV") {
  1186. temp = temp.parentNode;
  1187. }
  1188. }
  1189.  
  1190. return temp;
  1191. })();
  1192.  
  1193. item.avatarC.style.display = "";
  1194. item.containerC.style.display = "";
  1195. item.contentC.innerHTML = item.contentB;
  1196.  
  1197. if (item.actionC) {
  1198. item.actionC.style = "background: #aaa;";
  1199.  
  1200. if (uid < 0) {
  1201. item.actionC.style.display = "none";
  1202. }
  1203. }
  1204.  
  1205. if (filterMode === "标记") {
  1206. item.avatarC.style.display = "none";
  1207. item.contentC.innerHTML = `
  1208. <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
  1209. <span class="crimson">Troll must die.</span>
  1210. <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
  1211. <div style="display: none;" name="troll_${uid}">
  1212. ${item.contentB}
  1213. </div>
  1214. </div>`;
  1215.  
  1216. if (item.actionC && data.users[uid]) {
  1217. item.actionC.style = "background: #cb4042;";
  1218. }
  1219. } else if (filterMode === "遮罩") {
  1220. const caption = document.createElement("CAPTION");
  1221.  
  1222. if (item.i >= 0) {
  1223. caption.className = "filter-mask filter-mask-block";
  1224. } else {
  1225. caption.className = "filter-mask filter-mask-block left";
  1226. caption.style = "width: 47%;";
  1227. }
  1228.  
  1229. caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
  1230. caption.onclick = () => {
  1231. item.containerC.parentNode.removeChild(caption);
  1232. item.containerC.style.display = "";
  1233. };
  1234.  
  1235. item.containerC.parentNode.insertBefore(
  1236. caption,
  1237. item.containerC
  1238. );
  1239. item.containerC.style.display = "none";
  1240.  
  1241. if (item.actionC && data.users[uid]) {
  1242. item.actionC.style = "background: #cb4042;";
  1243. }
  1244. } else if (filterMode === "隐藏") {
  1245. item.containerC.style.display = "none";
  1246. } else {
  1247. await handleQuote(item.contentC);
  1248. }
  1249.  
  1250. if (item.tagC) {
  1251. const tags = data.users[uid]
  1252. ? data.users[uid].tags.map((tag) => data.tags[tag]) || []
  1253. : [];
  1254.  
  1255. item.tagC.style.display = tags.length ? "" : "none";
  1256. item.tagC.innerHTML = tags
  1257. .map(
  1258. (tag) =>
  1259. `<b class="block_txt nobr" style="background:${tag.color}; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>`
  1260. )
  1261. .join("");
  1262.  
  1263. witchHunter.run(uid, item.tagC);
  1264. }
  1265. });
  1266.  
  1267. if (item.actionC) {
  1268. item.actionC.onclick =
  1269. item.actionC.onclick ||
  1270. ((e) => {
  1271. if (item.pAid < 0) return;
  1272.  
  1273. const user = data.users[item.pAid];
  1274.  
  1275. if (e.ctrlKey === false) {
  1276. editUser(item.pAid, item.pName, item.reFilter);
  1277. } else {
  1278. if (user) {
  1279. delete data.users[user.id];
  1280. } else {
  1281. addUser(item.pAid, item.pName);
  1282. }
  1283.  
  1284. saveData();
  1285. item.reFilter();
  1286. }
  1287. });
  1288. }
  1289.  
  1290. await item.reFilter();
  1291. })
  1292. );
  1293. }
  1294. };
  1295.  
  1296. const execute = () =>
  1297. func().finally(() => {
  1298. if (hasNext) {
  1299. hasNext = false;
  1300.  
  1301. execute();
  1302. } else {
  1303. isRunning = false;
  1304. }
  1305. });
  1306.  
  1307. return async () => {
  1308. if (isRunning) {
  1309. hasNext = true;
  1310. } else {
  1311. isRunning = true;
  1312.  
  1313. await execute();
  1314. }
  1315. };
  1316. })();
  1317.  
  1318. // STYLE
  1319. GM_addStyle(`
  1320. .filter-table-wrapper {
  1321. max-height: 80vh;
  1322. overflow-y: auto;
  1323. }
  1324. .filter-table {
  1325. margin: 0;
  1326. }
  1327. .filter-table th,
  1328. .filter-table td {
  1329. position: relative;
  1330. white-space: nowrap;
  1331. }
  1332. .filter-table th {
  1333. position: sticky;
  1334. top: 2px;
  1335. z-index: 1;
  1336. }
  1337. .filter-table input:not([type]), .filter-table input[type="text"] {
  1338. margin: 0;
  1339. box-sizing: border-box;
  1340. height: 100%;
  1341. width: 100%;
  1342. }
  1343. .filter-input-wrapper {
  1344. position: absolute;
  1345. top: 6px;
  1346. right: 6px;
  1347. bottom: 6px;
  1348. left: 6px;
  1349. }
  1350. .filter-text-ellipsis {
  1351. display: flex;
  1352. }
  1353. .filter-text-ellipsis > * {
  1354. flex: 1;
  1355. width: 1px;
  1356. overflow: hidden;
  1357. text-overflow: ellipsis;
  1358. }
  1359. .filter-button-group {
  1360. margin: -.1em -.2em;
  1361. }
  1362. .filter-tags {
  1363. margin: 2px -0.2em 0;
  1364. text-align: left;
  1365. }
  1366. .filter-mask {
  1367. margin: 1px;
  1368. color: #81C7D4;
  1369. background: #81C7D4;
  1370. }
  1371. .filter-mask-block {
  1372. display: block;
  1373. border: 1px solid #66BAB7;
  1374. text-align: center !important;
  1375. }
  1376. .filter-input-wrapper {
  1377. position: absolute;
  1378. top: 6px;
  1379. right: 6px;
  1380. bottom: 6px;
  1381. left: 6px;
  1382. }
  1383. `);
  1384.  
  1385. // UI
  1386. const u = (() => {
  1387. const modules = {};
  1388.  
  1389. const tabContainer = (() => {
  1390. const c = document.createElement("div");
  1391.  
  1392. c.className = "w100";
  1393. c.innerHTML = `
  1394. <div class="right_" style="margin-bottom: 5px;">
  1395. <table class="stdbtn" cellspacing="0">
  1396. <tbody>
  1397. <tr></tr>
  1398. </tbody>
  1399. </table>
  1400. </div>
  1401. <div class="clear"></div>
  1402. `;
  1403.  
  1404. return c;
  1405. })();
  1406.  
  1407. const tabPanelContainer = (() => {
  1408. const c = document.createElement("div");
  1409.  
  1410. c.style = "width: 80vw;";
  1411.  
  1412. return c;
  1413. })();
  1414.  
  1415. const content = (() => {
  1416. const c = document.createElement("div");
  1417.  
  1418. c.append(tabContainer);
  1419. c.append(tabPanelContainer);
  1420.  
  1421. return c;
  1422. })();
  1423.  
  1424. const addModule = (() => {
  1425. const tc = tabContainer.getElementsByTagName("tr")[0];
  1426. const cc = tabPanelContainer;
  1427.  
  1428. return (module) => {
  1429. const tabBox = document.createElement("td");
  1430.  
  1431. tabBox.innerHTML = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`;
  1432.  
  1433. const tab = tabBox.childNodes[0];
  1434.  
  1435. const toggle = () => {
  1436. Object.values(modules).forEach((item) => {
  1437. if (item.tab === tab) {
  1438. item.tab.className = "nobr";
  1439. item.content.style = "display: block";
  1440. item.refresh();
  1441. } else {
  1442. item.tab.className = "nobr silver";
  1443. item.content.style = "display: none";
  1444. }
  1445. });
  1446. };
  1447.  
  1448. tc.append(tabBox);
  1449. cc.append(module.content);
  1450.  
  1451. tab.onclick = toggle;
  1452.  
  1453. modules[module.name] = {
  1454. ...module,
  1455. tab,
  1456. toggle,
  1457. };
  1458.  
  1459. return modules[module.name];
  1460. };
  1461. })();
  1462.  
  1463. return {
  1464. content,
  1465. modules,
  1466. addModule,
  1467. };
  1468. })();
  1469.  
  1470. // 用户
  1471. const userModule = (() => {
  1472. const content = (() => {
  1473. const c = document.createElement("div");
  1474.  
  1475. c.style = "display: none";
  1476. c.innerHTML = `
  1477. <div class="filter-table-wrapper">
  1478. <table class="filter-table forumbox">
  1479. <thead>
  1480. <tr class="block_txt_c0">
  1481. <th class="c1" width="1">昵称</th>
  1482. <th class="c2">标记</th>
  1483. <th class="c3" width="1">过滤方式</th>
  1484. <th class="c4" width="1">操作</th>
  1485. </tr>
  1486. </thead>
  1487. <tbody></tbody>
  1488. </table>
  1489. </div>
  1490. `;
  1491.  
  1492. return c;
  1493. })();
  1494.  
  1495. const refresh = (() => {
  1496. const container = content.getElementsByTagName("tbody")[0];
  1497.  
  1498. const func = () => {
  1499. container.innerHTML = "";
  1500.  
  1501. Object.values(data.users).forEach((item) => {
  1502. const tc = document.createElement("tr");
  1503.  
  1504. tc.className = `row${
  1505. (container.querySelectorAll("TR").length % 2) + 1
  1506. }`;
  1507.  
  1508. tc.refresh = () => {
  1509. if (data.users[item.id]) {
  1510. tc.innerHTML = `
  1511. <td class="c1">
  1512. <a href="/nuke.php?func=ucp&uid=${
  1513. item.id
  1514. }" class="b nobr">[${
  1515. item.name ? "@" + item.name : "#" + item.id
  1516. }]</a>
  1517. </td>
  1518. <td class="c2">
  1519. ${item.tags
  1520. .map((tag) => {
  1521. if (data.tags[tag]) {
  1522. return `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`;
  1523. }
  1524. })
  1525. .join("")}
  1526. </td>
  1527. <td class="c3">
  1528. <div class="filter-table-button-group">
  1529. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1530. </div>
  1531. </td>
  1532. <td class="c4">
  1533. <div class="filter-table-button-group">
  1534. <button>编辑</button>
  1535. <button>删除</button>
  1536. </div>
  1537. </td>
  1538. `;
  1539.  
  1540. const actions = tc.getElementsByTagName("button");
  1541.  
  1542. actions[0].onclick = () => {
  1543. data.users[item.id].filterMode = switchFilterMode(
  1544. data.users[item.id].filterMode || FILTER_MODE[0]
  1545. );
  1546.  
  1547. actions[0].innerHTML = data.users[item.id].filterMode;
  1548.  
  1549. saveData();
  1550. reFilter();
  1551. };
  1552.  
  1553. actions[1].onclick = () => {
  1554. editUser(item.id, item.name, tc.refresh);
  1555. };
  1556.  
  1557. actions[2].onclick = () => {
  1558. if (confirm("是否确认?")) {
  1559. delete data.users[item.id];
  1560. container.removeChild(tc);
  1561.  
  1562. saveData();
  1563. reFilter();
  1564. }
  1565. };
  1566. } else {
  1567. tc.remove();
  1568. }
  1569. };
  1570.  
  1571. tc.refresh();
  1572.  
  1573. container.appendChild(tc);
  1574. });
  1575. };
  1576.  
  1577. return func;
  1578. })();
  1579.  
  1580. return {
  1581. name: "用户",
  1582. content,
  1583. refresh,
  1584. };
  1585. })();
  1586.  
  1587. // 标记
  1588. const tagModule = (() => {
  1589. const content = (() => {
  1590. const c = document.createElement("div");
  1591.  
  1592. c.style = "display: none";
  1593. c.innerHTML = `
  1594. <div class="filter-table-wrapper">
  1595. <table class="filter-table forumbox">
  1596. <thead>
  1597. <tr class="block_txt_c0">
  1598. <th class="c1" width="1">标记</th>
  1599. <th class="c2">列表</th>
  1600. <th class="c3" width="1">过滤方式</th>
  1601. <th class="c4" width="1">操作</th>
  1602. </tr>
  1603. </thead>
  1604. <tbody></tbody>
  1605. </table>
  1606. </div>
  1607. `;
  1608.  
  1609. return c;
  1610. })();
  1611.  
  1612. const refresh = (() => {
  1613. const container = content.getElementsByTagName("tbody")[0];
  1614.  
  1615. const func = () => {
  1616. container.innerHTML = "";
  1617.  
  1618. Object.values(data.tags).forEach((item) => {
  1619. const tc = document.createElement("tr");
  1620.  
  1621. tc.className = `row${
  1622. (container.querySelectorAll("TR").length % 2) + 1
  1623. }`;
  1624.  
  1625. tc.innerHTML = `
  1626. <td class="c1">
  1627. <b class="block_txt nobr" style="background:${
  1628. item.color
  1629. }; color:#fff; margin: 0.1em 0.2em;">${item.name}</b>
  1630. </td>
  1631. <td class="c2">
  1632. <button>${
  1633. Object.values(data.users).filter((user) =>
  1634. user.tags.find((tag) => tag === item.id)
  1635. ).length
  1636. }
  1637. </button>
  1638. <div style="white-space: normal; display: none;">
  1639. ${Object.values(data.users)
  1640. .filter((user) =>
  1641. user.tags.find((tag) => tag === item.id)
  1642. )
  1643. .map(
  1644. (user) =>
  1645. `<a href="/nuke.php?func=ucp&uid=${
  1646. user.id
  1647. }" class="b nobr">[${
  1648. user.name ? "@" + user.name : "#" + user.id
  1649. }]</a>`
  1650. )
  1651. .join("")}
  1652. </div>
  1653. </td>
  1654. <td class="c3">
  1655. <div class="filter-table-button-group">
  1656. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1657. </div>
  1658. </td>
  1659. <td class="c4">
  1660. <div class="filter-table-button-group">
  1661. <button>删除</button>
  1662. </div>
  1663. </td>
  1664. `;
  1665.  
  1666. const actions = tc.getElementsByTagName("button");
  1667.  
  1668. actions[0].onclick = (() => {
  1669. let hide = true;
  1670. return () => {
  1671. hide = !hide;
  1672. actions[0].nextElementSibling.style.display = hide
  1673. ? "none"
  1674. : "block";
  1675. };
  1676. })();
  1677.  
  1678. actions[1].onclick = () => {
  1679. data.tags[item.id].filterMode = switchFilterMode(
  1680. data.tags[item.id].filterMode || FILTER_MODE[0]
  1681. );
  1682.  
  1683. actions[1].innerHTML = data.tags[item.id].filterMode;
  1684.  
  1685. saveData();
  1686. reFilter();
  1687. };
  1688.  
  1689. actions[2].onclick = () => {
  1690. if (confirm("是否确认?")) {
  1691. delete data.tags[item.id];
  1692.  
  1693. Object.values(data.users).forEach((user) => {
  1694. const index = user.tags.findIndex((tag) => tag === item.id);
  1695. if (index >= 0) {
  1696. user.tags.splice(index, 1);
  1697. }
  1698. });
  1699.  
  1700. container.removeChild(tc);
  1701.  
  1702. saveData();
  1703. reFilter();
  1704. }
  1705. };
  1706.  
  1707. container.appendChild(tc);
  1708. });
  1709. };
  1710.  
  1711. return func;
  1712. })();
  1713.  
  1714. return {
  1715. name: "标记",
  1716. content,
  1717. refresh,
  1718. };
  1719. })();
  1720.  
  1721. // 关键字
  1722. const keywordModule = (() => {
  1723. const content = (() => {
  1724. const c = document.createElement("div");
  1725.  
  1726. c.style = "display: none";
  1727. c.innerHTML = `
  1728. <div class="filter-table-wrapper">
  1729. <table class="filter-table forumbox">
  1730. <thead>
  1731. <tr class="block_txt_c0">
  1732. <th class="c1">列表</th>
  1733. <th class="c2" width="1">过滤方式</th>
  1734. <th class="c3" width="1">包括内容</th>
  1735. <th class="c4" width="1">操作</th>
  1736. </tr>
  1737. </thead>
  1738. <tbody></tbody>
  1739. </table>
  1740. </div>
  1741. <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用&quot;|&quot;隔开,&quot;ABC|DEF&quot;即为屏蔽带有ABC或者DEF的内容。</div>
  1742. `;
  1743.  
  1744. return c;
  1745. })();
  1746.  
  1747. const refresh = (() => {
  1748. const container = content.getElementsByTagName("tbody")[0];
  1749.  
  1750. const func = () => {
  1751. container.innerHTML = "";
  1752.  
  1753. Object.values(data.keywords).forEach((item) => {
  1754. const tc = document.createElement("tr");
  1755.  
  1756. tc.className = `row${
  1757. (container.querySelectorAll("TR").length % 2) + 1
  1758. }`;
  1759.  
  1760. tc.innerHTML = `
  1761. <td class="c1">
  1762. <div class="filter-input-wrapper">
  1763. <input value="${item.keyword || ""}" />
  1764. </div>
  1765. </td>
  1766. <td class="c2">
  1767. <div class="filter-table-button-group">
  1768. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1769. </div>
  1770. </td>
  1771. <td class="c3">
  1772. <div style="text-align: center;">
  1773. <input type="checkbox" ${
  1774. item.filterLevel ? `checked="checked"` : ""
  1775. } />
  1776. </div>
  1777. </td>
  1778. <td class="c4">
  1779. <div class="filter-table-button-group">
  1780. <button>保存</button>
  1781. <button>删除</button>
  1782. </div>
  1783. </td>
  1784. `;
  1785.  
  1786. const inputElement = tc.querySelector("INPUT");
  1787. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1788. const actions = tc.getElementsByTagName("button");
  1789.  
  1790. actions[0].onclick = () => {
  1791. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1792. };
  1793.  
  1794. actions[1].onclick = () => {
  1795. if (inputElement.value) {
  1796. data.keywords[item.id] = {
  1797. id: item.id,
  1798. keyword: inputElement.value,
  1799. filterMode: actions[0].innerHTML,
  1800. filterLevel: levelElement.checked ? 1 : 0,
  1801. };
  1802.  
  1803. saveData();
  1804. refresh();
  1805. }
  1806. };
  1807.  
  1808. actions[2].onclick = () => {
  1809. if (confirm("是否确认?")) {
  1810. delete data.keywords[item.id];
  1811.  
  1812. saveData();
  1813. refresh();
  1814. }
  1815. };
  1816.  
  1817. container.appendChild(tc);
  1818. });
  1819.  
  1820. {
  1821. const tc = document.createElement("tr");
  1822.  
  1823. tc.className = `row${
  1824. (container.querySelectorAll("TR").length % 2) + 1
  1825. }`;
  1826.  
  1827. tc.innerHTML = `
  1828. <td class="c1">
  1829. <div class="filter-input-wrapper">
  1830. <input value="" />
  1831. </div>
  1832. </td>
  1833. <td class="c2">
  1834. <div class="filter-table-button-group">
  1835. <button>${FILTER_MODE[0]}</button>
  1836. </div>
  1837. </td>
  1838. <td class="c3">
  1839. <div style="text-align: center;">
  1840. <input type="checkbox" />
  1841. </div>
  1842. </td>
  1843. <td class="c4">
  1844. <div class="filter-table-button-group">
  1845. <button>添加</button>
  1846. </div>
  1847. </td>
  1848. `;
  1849.  
  1850. const inputElement = tc.querySelector("INPUT");
  1851. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1852. const actions = tc.getElementsByTagName("button");
  1853.  
  1854. actions[0].onclick = () => {
  1855. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1856. };
  1857.  
  1858. actions[1].onclick = () => {
  1859. if (inputElement.value) {
  1860. addKeyword(
  1861. inputElement.value,
  1862. actions[0].innerHTML,
  1863. levelElement.checked ? 1 : 0
  1864. );
  1865.  
  1866. saveData();
  1867. refresh();
  1868. }
  1869. };
  1870.  
  1871. container.appendChild(tc);
  1872. }
  1873. };
  1874.  
  1875. return func;
  1876. })();
  1877.  
  1878. return {
  1879. name: "关键字",
  1880. content,
  1881. refresh,
  1882. };
  1883. })();
  1884.  
  1885. // 属地
  1886. const locationModule = (() => {
  1887. const content = (() => {
  1888. const c = document.createElement("div");
  1889.  
  1890. c.style = "display: none";
  1891. c.innerHTML = `
  1892. <div class="filter-table-wrapper">
  1893. <table class="filter-table forumbox">
  1894. <thead>
  1895. <tr class="block_txt_c0">
  1896. <th class="c1">列表</th>
  1897. <th class="c2" width="1">过滤方式</th>
  1898. <th class="c3" width="1">操作</th>
  1899. </tr>
  1900. </thead>
  1901. <tbody></tbody>
  1902. </table>
  1903. </div>
  1904. <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用&quot;|&quot;隔开,&quot;ABC|DEF&quot;即为屏蔽带有ABC或者DEF的内容。<br/>属地过滤功能需要占用额外的资源,请谨慎开启</div>
  1905. `;
  1906.  
  1907. return c;
  1908. })();
  1909.  
  1910. const refresh = (() => {
  1911. const container = content.getElementsByTagName("tbody")[0];
  1912.  
  1913. const func = () => {
  1914. container.innerHTML = "";
  1915.  
  1916. Object.values(data.locations).forEach((item) => {
  1917. const tc = document.createElement("tr");
  1918.  
  1919. tc.className = `row${
  1920. (container.querySelectorAll("TR").length % 2) + 1
  1921. }`;
  1922.  
  1923. tc.innerHTML = `
  1924. <td class="c1">
  1925. <div class="filter-input-wrapper">
  1926. <input value="${item.keyword || ""}" />
  1927. </div>
  1928. </td>
  1929. <td class="c2">
  1930. <div class="filter-table-button-group">
  1931. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1932. </div>
  1933. </td>
  1934. <td class="c3">
  1935. <div class="filter-table-button-group">
  1936. <button>保存</button>
  1937. <button>删除</button>
  1938. </div>
  1939. </td>
  1940. `;
  1941.  
  1942. const inputElement = tc.querySelector("INPUT");
  1943. const actions = tc.getElementsByTagName("button");
  1944.  
  1945. actions[0].onclick = () => {
  1946. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1947. };
  1948.  
  1949. actions[1].onclick = () => {
  1950. if (inputElement.value) {
  1951. data.locations[item.id] = {
  1952. id: item.id,
  1953. keyword: inputElement.value,
  1954. filterMode: actions[0].innerHTML,
  1955. };
  1956.  
  1957. saveData();
  1958. refresh();
  1959. }
  1960. };
  1961.  
  1962. actions[2].onclick = () => {
  1963. if (confirm("是否确认?")) {
  1964. delete data.locations[item.id];
  1965.  
  1966. saveData();
  1967. refresh();
  1968. }
  1969. };
  1970.  
  1971. container.appendChild(tc);
  1972. });
  1973.  
  1974. {
  1975. const tc = document.createElement("tr");
  1976.  
  1977. tc.className = `row${
  1978. (container.querySelectorAll("TR").length % 2) + 1
  1979. }`;
  1980.  
  1981. tc.innerHTML = `
  1982. <td class="c1">
  1983. <div class="filter-input-wrapper">
  1984. <input value="" />
  1985. </div>
  1986. </td>
  1987. <td class="c2">
  1988. <div class="filter-table-button-group">
  1989. <button>${FILTER_MODE[0]}</button>
  1990. </div>
  1991. </td>
  1992. <td class="c3">
  1993. <div class="filter-table-button-group">
  1994. <button>添加</button>
  1995. </div>
  1996. </td>
  1997. `;
  1998.  
  1999. const inputElement = tc.querySelector("INPUT");
  2000. const actions = tc.getElementsByTagName("button");
  2001.  
  2002. actions[0].onclick = () => {
  2003. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  2004. };
  2005.  
  2006. actions[1].onclick = () => {
  2007. if (inputElement.value) {
  2008. addLocation(inputElement.value, actions[0].innerHTML);
  2009.  
  2010. saveData();
  2011. refresh();
  2012. }
  2013. };
  2014.  
  2015. container.appendChild(tc);
  2016. }
  2017. };
  2018.  
  2019. return func;
  2020. })();
  2021.  
  2022. return {
  2023. name: "属地",
  2024. content,
  2025. refresh,
  2026. };
  2027. })();
  2028.  
  2029. // 猎巫
  2030. const witchHuntModule = (() => {
  2031. const content = (() => {
  2032. const c = document.createElement("div");
  2033.  
  2034. c.style = "display: none";
  2035. c.innerHTML = `
  2036. <div class="filter-table-wrapper">
  2037. <table class="filter-table forumbox">
  2038. <thead>
  2039. <tr class="block_txt_c0">
  2040. <th class="c1">版面</th>
  2041. <th class="c2">标签</th>
  2042. <th class="c3" width="1">操作</th>
  2043. </tr>
  2044. </thead>
  2045. <tbody></tbody>
  2046. </table>
  2047. </div>
  2048. <div class="silver" style="margin-top: 10px;">猎巫模块需要占用额外的资源,请谨慎开启<br/>该功能为实验性功能,仅判断用户是否曾经在某个版面发言<br/>未来可能会加入发言的筛选或是屏蔽功能,也可能移除此功能</div>
  2049. `;
  2050.  
  2051. return c;
  2052. })();
  2053.  
  2054. const refresh = (() => {
  2055. const container = content.getElementsByTagName("tbody")[0];
  2056.  
  2057. const func = () => {
  2058. container.innerHTML = "";
  2059.  
  2060. Object.values(witchHunter.data).forEach((item, index) => {
  2061. const tc = document.createElement("tr");
  2062.  
  2063. tc.className = `row${
  2064. (container.querySelectorAll("TR").length % 2) + 1
  2065. }`;
  2066.  
  2067. tc.innerHTML = `
  2068. <td class="c1">
  2069. <div class="filter-input-wrapper">
  2070. <a href="/thread.php?fid=${item.fid}" class="b nobr">[${item.name}]</a>
  2071. </div>
  2072. </td>
  2073. <td class="c2">
  2074. <b class="block_txt nobr" style="background:${item.color}; color:#fff; margin: 0.1em 0.2em;">${item.label}</b>
  2075. </td>
  2076. <td class="c3">
  2077. <div class="filter-table-button-group">
  2078. <button>删除</button>
  2079. </div>
  2080. </td>
  2081. `;
  2082.  
  2083. const actions = tc.getElementsByTagName("button");
  2084.  
  2085. actions[0].onclick = () => {
  2086. if (confirm("是否确认?")) {
  2087. witchHunter.remove(item.id);
  2088.  
  2089. refresh();
  2090. }
  2091. };
  2092.  
  2093. container.appendChild(tc);
  2094. });
  2095.  
  2096. {
  2097. const tc = document.createElement("tr");
  2098.  
  2099. tc.className = `row${
  2100. (container.querySelectorAll("TR").length % 2) + 1
  2101. }`;
  2102.  
  2103. tc.innerHTML = `
  2104. <td class="c1">
  2105. <div class="filter-input-wrapper">
  2106. <input value="" placeholder="版面ID" />
  2107. </div>
  2108. </td>
  2109. <td class="c2">
  2110. <div class="filter-input-wrapper">
  2111. <input value="" />
  2112. </div>
  2113. </td>
  2114. <td class="c3">
  2115. <div class="filter-table-button-group">
  2116. <button>添加</button>
  2117. </div>
  2118. </td>
  2119. `;
  2120.  
  2121. const inputElement = tc.getElementsByTagName("INPUT");
  2122. const actions = tc.getElementsByTagName("button");
  2123.  
  2124. actions[0].onclick = async () => {
  2125. const fid = parseInt(inputElement[0].value, 10);
  2126. const tag = inputElement[1].value.trim();
  2127.  
  2128. if (isNaN(fid) || tag.length === 0) {
  2129. return;
  2130. }
  2131.  
  2132. await witchHunter.add(fid, tag);
  2133.  
  2134. refresh();
  2135. };
  2136.  
  2137. container.appendChild(tc);
  2138. }
  2139. };
  2140.  
  2141. return func;
  2142. })();
  2143.  
  2144. return {
  2145. name: "猎巫",
  2146. content,
  2147. refresh,
  2148. };
  2149. })();
  2150.  
  2151. // 通用设置
  2152. const commonModule = (() => {
  2153. const content = (() => {
  2154. const c = document.createElement("div");
  2155.  
  2156. c.style = "display: none";
  2157.  
  2158. return c;
  2159. })();
  2160.  
  2161. const refresh = (() => {
  2162. const container = content;
  2163.  
  2164. const func = () => {
  2165. container.innerHTML = "";
  2166.  
  2167. // 默认过滤方式
  2168. {
  2169. const tc = document.createElement("div");
  2170.  
  2171. tc.innerHTML += `
  2172. <div>默认过滤方式</div>
  2173. <div></div>
  2174. <div class="silver" style="margin-top: 10px;">${FILTER_TIPS}</div>
  2175. `;
  2176.  
  2177. ["标记", "遮罩", "隐藏"].forEach((item, index) => {
  2178. const ele = document.createElement("SPAN");
  2179.  
  2180. ele.innerHTML += `
  2181. <input id="s-fm-${index}" type="radio" name="filterType" ${
  2182. data.options.filterMode === item && "checked"
  2183. }>
  2184. <label for="s-fm-${index}" style="cursor: pointer;">${item}</label>
  2185. `;
  2186.  
  2187. const inp = ele.querySelector("input");
  2188.  
  2189. inp.onchange = () => {
  2190. if (inp.checked) {
  2191. data.options.filterMode = item;
  2192. saveData();
  2193. reFilter();
  2194. }
  2195. };
  2196.  
  2197. tc.querySelectorAll("div")[1].append(ele);
  2198. });
  2199.  
  2200. container.appendChild(tc);
  2201. }
  2202.  
  2203. // 小号过滤(时间)
  2204. {
  2205. const tc = document.createElement("div");
  2206.  
  2207. tc.innerHTML += `
  2208. <br/>
  2209. <div>
  2210. 隐藏注册时间小于<input value="${
  2211. (data.options.filterRegdateLimit || 0) / 86400000
  2212. }" maxLength="4" style="width: 48px;" />天的用户
  2213. <button>确认</button>
  2214. </div>
  2215. `;
  2216.  
  2217. const actions = tc.getElementsByTagName("button");
  2218.  
  2219. actions[0].onclick = () => {
  2220. const v = actions[0].previousElementSibling.value;
  2221.  
  2222. const n = Number(v) || 0;
  2223.  
  2224. data.options.filterRegdateLimit = n < 0 ? 0 : n * 86400000;
  2225.  
  2226. saveData();
  2227. reFilter();
  2228. };
  2229.  
  2230. container.appendChild(tc);
  2231. }
  2232.  
  2233. // 小号过滤(发帖数)
  2234. {
  2235. const tc = document.createElement("div");
  2236.  
  2237. tc.innerHTML += `
  2238. <br/>
  2239. <div>
  2240. 隐藏发帖数量小于<input value="${
  2241. data.options.filterPostnumLimit || 0
  2242. }" maxLength="5" style="width: 48px;" />贴的用户
  2243. <button>确认</button>
  2244. </div>
  2245. `;
  2246.  
  2247. const actions = tc.getElementsByTagName("button");
  2248.  
  2249. actions[0].onclick = () => {
  2250. const v = actions[0].previousElementSibling.value;
  2251.  
  2252. const n = Number(v) || 0;
  2253.  
  2254. data.options.filterPostnumLimit = n < 0 ? 0 : n;
  2255.  
  2256. saveData();
  2257. reFilter();
  2258. };
  2259.  
  2260. container.appendChild(tc);
  2261. }
  2262.  
  2263. // 声望过滤
  2264. {
  2265. const tc = document.createElement("div");
  2266.  
  2267. tc.innerHTML += `
  2268. <br/>
  2269. <div>
  2270. 隐藏版面声望低于<input value="${
  2271. data.options.filterReputationLimit || ""
  2272. }" maxLength="5" style="width: 48px;" />点的用户
  2273. <button>确认</button>
  2274. </div>
  2275. `;
  2276.  
  2277. const actions = tc.getElementsByTagName("button");
  2278.  
  2279. actions[0].onclick = () => {
  2280. const v = actions[0].previousElementSibling.value;
  2281.  
  2282. const n = Number(v);
  2283.  
  2284. data.options.filterReputationLimit = n;
  2285.  
  2286. saveData();
  2287. reFilter();
  2288. };
  2289.  
  2290. container.appendChild(tc);
  2291. }
  2292.  
  2293. // 匿名过滤
  2294. {
  2295. const tc = document.createElement("div");
  2296.  
  2297. tc.innerHTML += `
  2298. <br/>
  2299. <div>
  2300. <label>
  2301. 隐藏匿名的用户
  2302. <input type="checkbox" ${
  2303. data.options.filterAnony ? `checked="checked"` : ""
  2304. } />
  2305. </label>
  2306. </div>
  2307. `;
  2308.  
  2309. const checkbox = tc.querySelector("input");
  2310.  
  2311. checkbox.onchange = () => {
  2312. const v = checkbox.checked;
  2313.  
  2314. data.options.filterAnony = v;
  2315.  
  2316. saveData();
  2317. reFilter();
  2318. };
  2319.  
  2320. container.appendChild(tc);
  2321. }
  2322.  
  2323. // 删除没有标记的用户
  2324. {
  2325. const tc = document.createElement("div");
  2326.  
  2327. tc.innerHTML += `
  2328. <br/>
  2329. <div>
  2330. <button>删除没有标记的用户</button>
  2331. </div>
  2332. `;
  2333.  
  2334. const actions = tc.getElementsByTagName("button");
  2335.  
  2336. actions[0].onclick = () => {
  2337. if (confirm("是否确认?")) {
  2338. Object.values(data.users).forEach((item) => {
  2339. if (item.tags.length === 0) {
  2340. delete data.users[item.id];
  2341. }
  2342. });
  2343.  
  2344. saveData();
  2345. reFilter();
  2346. }
  2347. };
  2348.  
  2349. container.appendChild(tc);
  2350. }
  2351.  
  2352. // 删除没有用户的标记
  2353. {
  2354. const tc = document.createElement("div");
  2355.  
  2356. tc.innerHTML += `
  2357. <br/>
  2358. <div>
  2359. <button>删除没有用户的标记</button>
  2360. </div>
  2361. `;
  2362.  
  2363. const actions = tc.getElementsByTagName("button");
  2364.  
  2365. actions[0].onclick = () => {
  2366. if (confirm("是否确认?")) {
  2367. Object.values(data.tags).forEach((item) => {
  2368. if (
  2369. Object.values(data.users).filter((user) =>
  2370. user.tags.find((tag) => tag === item.id)
  2371. ).length === 0
  2372. ) {
  2373. delete data.tags[item.id];
  2374. }
  2375. });
  2376.  
  2377. saveData();
  2378. reFilter();
  2379. }
  2380. };
  2381.  
  2382. container.appendChild(tc);
  2383. }
  2384.  
  2385. // 删除非激活中的用户
  2386. {
  2387. const tc = document.createElement("div");
  2388.  
  2389. tc.innerHTML += `
  2390. <br/>
  2391. <div>
  2392. <button>删除非激活中的用户</button>
  2393. <div style="white-space: normal;"></div>
  2394. </div>
  2395. `;
  2396.  
  2397. const action = tc.querySelector("button");
  2398. const list = action.nextElementSibling;
  2399.  
  2400. action.onclick = () => {
  2401. if (confirm("是否确认?")) {
  2402. const waitingQueue = Object.values(data.users).map(
  2403. (item) => () =>
  2404. new Promise((resolve) => {
  2405. fetch(
  2406. `/nuke.php?lite=js&__lib=ucp&__act=get&uid=${item.id}`
  2407. )
  2408. .then((res) => res.blob())
  2409. .then((blob) => {
  2410. const reader = new FileReader();
  2411.  
  2412. reader.onload = () => {
  2413. const text = reader.result;
  2414. const result = JSON.parse(
  2415. text.replace(
  2416. "window.script_muti_get_var_store=",
  2417. ""
  2418. )
  2419. );
  2420.  
  2421. if (!result.error) {
  2422. const { bit } = result.data[0];
  2423.  
  2424. const activeInfo = n.activeInfo(0, 0, bit);
  2425.  
  2426. const activeType = activeInfo[1];
  2427.  
  2428. if (!["ACTIVED", "LINKED"].includes(activeType)) {
  2429. list.innerHTML += `<a href="/nuke.php?func=ucp&uid=${
  2430. item.id
  2431. }" class="b nobr">[${
  2432. item.name ? "@" + item.name : "#" + item.id
  2433. }]</a>`;
  2434.  
  2435. delete data.users[item.id];
  2436. }
  2437. }
  2438.  
  2439. resolve();
  2440. };
  2441.  
  2442. reader.readAsText(blob, "GBK");
  2443. })
  2444. .catch(() => {
  2445. resolve();
  2446. });
  2447. })
  2448. );
  2449.  
  2450. const queueLength = waitingQueue.length;
  2451.  
  2452. const execute = () => {
  2453. if (waitingQueue.length) {
  2454. const next = waitingQueue.shift();
  2455.  
  2456. action.innerHTML = `删除非激活中的用户 (${
  2457. queueLength - waitingQueue.length
  2458. }/${queueLength})`;
  2459. action.disabled = true;
  2460.  
  2461. next().finally(execute);
  2462. } else {
  2463. action.disabled = false;
  2464.  
  2465. saveData();
  2466. reFilter();
  2467. }
  2468. };
  2469.  
  2470. execute();
  2471. }
  2472. };
  2473.  
  2474. container.appendChild(tc);
  2475. }
  2476. };
  2477.  
  2478. return func;
  2479. })();
  2480.  
  2481. return {
  2482. name: "通用设置",
  2483. content,
  2484. refresh,
  2485. };
  2486. })();
  2487.  
  2488. u.addModule(userModule).toggle();
  2489. u.addModule(tagModule);
  2490. u.addModule(keywordModule);
  2491. u.addModule(locationModule);
  2492. u.addModule(witchHuntModule);
  2493. u.addModule(commonModule);
  2494.  
  2495. // 增加菜单项
  2496. (() => {
  2497. const title = "过滤设置";
  2498.  
  2499. let window;
  2500.  
  2501. const container = document.createElement("DIV");
  2502.  
  2503. container.className = `td`;
  2504. container.innerHTML = `<a class="mmdefault" href="javascript: void(0);" style="white-space: nowrap;">屏蔽</a>`;
  2505.  
  2506. const content = container.querySelector("A");
  2507.  
  2508. const anchor = document.querySelector("#mainmenu .td:last-child");
  2509.  
  2510. anchor.before(container);
  2511.  
  2512. content.onclick = () => {
  2513. if (window === undefined) {
  2514. window = n.createCommmonWindow();
  2515. }
  2516.  
  2517. window._.addContent(null);
  2518. window._.addTitle(title);
  2519. window._.addContent(u.content);
  2520. window._.show();
  2521. };
  2522. })();
  2523.  
  2524. // 执行过滤
  2525. (() => {
  2526. const hookFunction = (object, functionName, callback) => {
  2527. ((originalFunction) => {
  2528. object[functionName] = function () {
  2529. const returnValue = originalFunction.apply(this, arguments);
  2530.  
  2531. callback.apply(this, [returnValue, originalFunction, arguments]);
  2532.  
  2533. return returnValue;
  2534. };
  2535. })(object[functionName]);
  2536. };
  2537.  
  2538. const initialized = {
  2539. topicArg: false,
  2540. postArg: false,
  2541. };
  2542.  
  2543. hookFunction(n, "eval", () => {
  2544. if (Object.values(initialized).findIndex((item) => item === false) < 0) {
  2545. return;
  2546. }
  2547.  
  2548. if (n.topicArg && initialized.topicArg === false) {
  2549. hookFunction(n.topicArg, "add", reFilter);
  2550.  
  2551. initialized.topicArg = true;
  2552. }
  2553.  
  2554. if (n.postArg && initialized.postArg === false) {
  2555. hookFunction(n.postArg, "proc", reFilter);
  2556.  
  2557. initialized.postArg = true;
  2558. }
  2559. });
  2560.  
  2561. reFilter();
  2562. })();
  2563. })(commonui, __CURRENT_UID);