NGA Filter

troll must die

当前为 2022-07-19 提交的版本,查看 最新版本

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