NGA Filter

troll must die

当前为 2022-09-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name NGA Filter
  3. // @namespace https://greasyfork.org/users/263018
  4. // @version 1.8.1
  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) {
  796. const params = new URLSearchParams(location.search);
  797. if (params.has('favor')) {
  798. return;
  799. }
  800. if (params.has('authorid')) {
  801. return;
  802. }
  803. }
  804.  
  805. if (tPage) {
  806. const tData = n.topicArg.data;
  807.  
  808. await Promise.all(
  809. Object.values(tData).map(async (item) => {
  810. if (item.containerC) return;
  811.  
  812. const tid = item[8];
  813.  
  814. const filterMode = await new Promise(async (resolve) => {
  815. const mode = await getFilterModeByTopic(tid);
  816.  
  817. if (mode === 0) {
  818. resolve(data.options.filterMode);
  819. }
  820.  
  821. if (mode > 0) {
  822. resolve(FILTER_MODE[mode]);
  823. }
  824.  
  825. resolve("");
  826. });
  827.  
  828. item.contentC = item[1];
  829.  
  830. item.contentB = item.contentB || item.contentC.innerHTML;
  831.  
  832. item.containerC =
  833. item.containerC || item.contentC.parentNode.parentNode;
  834.  
  835. item.containerC.style = "";
  836. item.contentC.style = "";
  837. item[1].className = item[1].className.replace(" filter-mask", "");
  838. item[2].className = item[2].className.replace(" filter-mask", "");
  839.  
  840. if (filterMode === "标记") {
  841. item.contentC.style = "text-decoration: line-through;";
  842. } else if (filterMode === "遮罩") {
  843. item[1].className += " filter-mask";
  844. item[2].className += " filter-mask";
  845. } else if (filterMode === "隐藏") {
  846. item.containerC.style = "display: none;";
  847. }
  848. })
  849. );
  850. } else if (pPage) {
  851. const pData = n.postArg.data;
  852.  
  853. await Promise.all(
  854. Object.values(pData).map(async (item) => {
  855. if (~~item.pAid === self) return;
  856. if (item.containerC) return;
  857.  
  858. if (typeof item.i === "number") {
  859. item.actionC =
  860. item.actionC ||
  861. (() => {
  862. const ele = item.uInfoC.querySelector('[name="uid"]');
  863.  
  864. ele.onclick = null;
  865.  
  866. return ele;
  867. })();
  868.  
  869. item.tagC =
  870. item.tagC ||
  871. (() => {
  872. const tc = document.createElement("div");
  873.  
  874. tc.className = "filter-tags";
  875.  
  876. item.uInfoC.appendChild(tc);
  877.  
  878. return tc;
  879. })();
  880. }
  881.  
  882. item.pName =
  883. item.pName ||
  884. item.uInfoC.getElementsByClassName("author")[0].innerText;
  885.  
  886. item.reFilter =
  887. item.reFilter ||
  888. (async () => {
  889. const uid = item.pAid;
  890.  
  891. const filterMode = await new Promise(async (resolve) => {
  892. const mode = await getFilterMode(
  893. uid,
  894. item.subjectC.innerText,
  895. item.contentC.innerText
  896. );
  897.  
  898. if (mode === 0) {
  899. resolve(data.options.filterMode);
  900. }
  901.  
  902. if (mode > 0) {
  903. resolve(FILTER_MODE[mode]);
  904. }
  905.  
  906. resolve("");
  907. });
  908.  
  909. item.avatarC =
  910. item.avatarC ||
  911. (() => {
  912. const tc = document.createElement("div");
  913.  
  914. const avatar = document.getElementById(
  915. `posteravatar${item.i}`
  916. );
  917.  
  918. if (avatar) {
  919. avatar.parentNode.insertBefore(tc, avatar.nextSibling);
  920.  
  921. tc.appendChild(avatar);
  922. }
  923.  
  924. return tc;
  925. })();
  926.  
  927. item.contentB = item.contentB || item.contentC.innerHTML;
  928.  
  929. item.containerC =
  930. item.containerC ||
  931. (() => {
  932. let temp = item.contentC;
  933.  
  934. if (item.i >= 0) {
  935. while (temp.nodeName !== "TBODY") {
  936. temp = temp.parentNode;
  937. }
  938. } else {
  939. while (temp.nodeName !== "DIV") {
  940. temp = temp.parentNode;
  941. }
  942. }
  943.  
  944. return temp;
  945. })();
  946.  
  947. item.avatarC.style.display = "";
  948. item.containerC.style.display = "";
  949. item.contentC.innerHTML = item.contentB;
  950.  
  951. if (item.actionC) {
  952. item.actionC.style = "background: #aaa;";
  953. }
  954.  
  955. if (filterMode === "标记") {
  956. item.avatarC.style.display = "none";
  957. item.contentC.innerHTML = `
  958. <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
  959. <span class="crimson">Troll must die.</span>
  960. <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
  961. <div style="display: none;" name="troll_${uid}">
  962. ${item.contentB}
  963. </div>
  964. </div>`;
  965.  
  966. if (item.actionC && data.users[uid]) {
  967. item.actionC.style = "background: #cb4042;";
  968. }
  969. } else if (filterMode === "遮罩") {
  970. const caption = document.createElement("CAPTION");
  971.  
  972. if (item.i >= 0) {
  973. caption.className = "filter-mask filter-mask-block";
  974. } else {
  975. caption.className = "filter-mask filter-mask-block left";
  976. caption.style = "width: 47%;";
  977. }
  978.  
  979. caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
  980. caption.onclick = () => {
  981. item.containerC.parentNode.removeChild(caption);
  982. item.containerC.style.display = "";
  983. };
  984.  
  985. item.containerC.parentNode.insertBefore(
  986. caption,
  987. item.containerC
  988. );
  989. item.containerC.style.display = "none";
  990.  
  991. if (item.actionC && data.users[uid]) {
  992. item.actionC.style = "background: #cb4042;";
  993. }
  994. } else if (filterMode === "隐藏") {
  995. item.containerC.style.display = "none";
  996. } else {
  997. await handleQuote(item.contentC);
  998. }
  999.  
  1000. if (item.tagC) {
  1001. const tags = data.users[uid]
  1002. ? data.users[uid].tags.map((tag) => data.tags[tag]) || []
  1003. : [];
  1004.  
  1005. item.tagC.style.display = tags.length ? "" : "none";
  1006. item.tagC.innerHTML = tags
  1007. .map(
  1008. (tag) =>
  1009. `<b class="block_txt nobr" style="background:${tag.color}; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>`
  1010. )
  1011. .join("");
  1012. }
  1013. });
  1014.  
  1015. if (item.actionC) {
  1016. item.actionC.onclick =
  1017. item.actionC.onclick ||
  1018. ((e) => {
  1019. if (item.pAid < 0) return;
  1020.  
  1021. const user = data.users[item.pAid];
  1022.  
  1023. if (e.ctrlKey === false) {
  1024. editUser(item.pAid, item.pName, item.reFilter);
  1025. } else {
  1026. if (user) {
  1027. delete data.users[user.id];
  1028. } else {
  1029. addUser(item.pAid, item.pName);
  1030. }
  1031.  
  1032. saveData();
  1033. item.reFilter();
  1034. }
  1035. });
  1036. }
  1037.  
  1038. await item.reFilter();
  1039. })
  1040. );
  1041. }
  1042. };
  1043.  
  1044. const execute = () =>
  1045. func().finally(() => {
  1046. if (hasNext) {
  1047. hasNext = false;
  1048.  
  1049. execute();
  1050. } else {
  1051. isRunning = false;
  1052. }
  1053. });
  1054.  
  1055. return async () => {
  1056. if (isRunning) {
  1057. hasNext = true;
  1058. } else {
  1059. isRunning = true;
  1060.  
  1061. await execute();
  1062. }
  1063. };
  1064. })();
  1065.  
  1066. // STYLE
  1067. GM_addStyle(`
  1068. .filter-table-wrapper {
  1069. max-height: 80vh;
  1070. overflow-y: auto;
  1071. }
  1072. .filter-table {
  1073. margin: 0;
  1074. }
  1075. .filter-table th,
  1076. .filter-table td {
  1077. position: relative;
  1078. white-space: nowrap;
  1079. }
  1080. .filter-table th {
  1081. position: sticky;
  1082. top: 2px;
  1083. z-index: 1;
  1084. }
  1085. .filter-table input:not([type]), .filter-table input[type="text"] {
  1086. margin: 0;
  1087. box-sizing: border-box;
  1088. height: 100%;
  1089. width: 100%;
  1090. }
  1091. .filter-input-wrapper {
  1092. position: absolute;
  1093. top: 6px;
  1094. right: 6px;
  1095. bottom: 6px;
  1096. left: 6px;
  1097. }
  1098. .filter-text-ellipsis {
  1099. display: flex;
  1100. }
  1101. .filter-text-ellipsis > * {
  1102. flex: 1;
  1103. width: 1px;
  1104. overflow: hidden;
  1105. text-overflow: ellipsis;
  1106. }
  1107. .filter-button-group {
  1108. margin: -.1em -.2em;
  1109. }
  1110. .filter-tags {
  1111. margin: 2px -0.2em 0;
  1112. text-align: left;
  1113. }
  1114. .filter-mask {
  1115. margin: 1px;
  1116. color: #81C7D4;
  1117. background: #81C7D4;
  1118. }
  1119. .filter-mask-block {
  1120. display: block;
  1121. border: 1px solid #66BAB7;
  1122. text-align: center !important;
  1123. }
  1124. .filter-input-wrapper {
  1125. position: absolute;
  1126. top: 6px;
  1127. right: 6px;
  1128. bottom: 6px;
  1129. left: 6px;
  1130. }
  1131. `);
  1132.  
  1133. // UI
  1134. const u = (() => {
  1135. const modules = {};
  1136.  
  1137. const tabContainer = (() => {
  1138. const c = document.createElement("div");
  1139.  
  1140. c.className = "w100";
  1141. c.innerHTML = `
  1142. <div class="right_" style="margin-bottom: 5px;">
  1143. <table class="stdbtn" cellspacing="0">
  1144. <tbody>
  1145. <tr></tr>
  1146. </tbody>
  1147. </table>
  1148. </div>
  1149. <div class="clear"></div>
  1150. `;
  1151.  
  1152. return c;
  1153. })();
  1154.  
  1155. const tabPanelContainer = (() => {
  1156. const c = document.createElement("div");
  1157.  
  1158. c.style = "width: 80vw;";
  1159.  
  1160. return c;
  1161. })();
  1162.  
  1163. const content = (() => {
  1164. const c = document.createElement("div");
  1165.  
  1166. c.append(tabContainer);
  1167. c.append(tabPanelContainer);
  1168.  
  1169. return c;
  1170. })();
  1171.  
  1172. const addModule = (() => {
  1173. const tc = tabContainer.getElementsByTagName("tr")[0];
  1174. const cc = tabPanelContainer;
  1175.  
  1176. return (module) => {
  1177. const tabBox = document.createElement("td");
  1178.  
  1179. tabBox.innerHTML = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`;
  1180.  
  1181. const tab = tabBox.childNodes[0];
  1182.  
  1183. const toggle = () => {
  1184. Object.values(modules).forEach((item) => {
  1185. if (item.tab === tab) {
  1186. item.tab.className = "nobr";
  1187. item.content.style = "display: block";
  1188. item.refresh();
  1189. } else {
  1190. item.tab.className = "nobr silver";
  1191. item.content.style = "display: none";
  1192. }
  1193. });
  1194. };
  1195.  
  1196. tc.append(tabBox);
  1197. cc.append(module.content);
  1198.  
  1199. tab.onclick = toggle;
  1200.  
  1201. modules[module.name] = {
  1202. ...module,
  1203. tab,
  1204. toggle,
  1205. };
  1206.  
  1207. return modules[module.name];
  1208. };
  1209. })();
  1210.  
  1211. return {
  1212. content,
  1213. modules,
  1214. addModule,
  1215. };
  1216. })();
  1217.  
  1218. // 用户
  1219. const userModule = (() => {
  1220. const content = (() => {
  1221. const c = document.createElement("div");
  1222.  
  1223. c.style = "display: none";
  1224. c.innerHTML = `
  1225. <div class="filter-table-wrapper">
  1226. <table class="filter-table forumbox">
  1227. <thead>
  1228. <tr class="block_txt_c0">
  1229. <th class="c1" width="1">昵称</th>
  1230. <th class="c2">标记</th>
  1231. <th class="c3" width="1">过滤方式</th>
  1232. <th class="c4" width="1">操作</th>
  1233. </tr>
  1234. </thead>
  1235. <tbody></tbody>
  1236. </table>
  1237. </div>
  1238. `;
  1239.  
  1240. return c;
  1241. })();
  1242.  
  1243. const refresh = (() => {
  1244. const container = content.getElementsByTagName("tbody")[0];
  1245.  
  1246. const func = () => {
  1247. container.innerHTML = "";
  1248.  
  1249. Object.values(data.users).forEach((item) => {
  1250. const tc = document.createElement("tr");
  1251.  
  1252. tc.className = `row${
  1253. (container.querySelectorAll("TR").length % 2) + 1
  1254. }`;
  1255.  
  1256. tc.refresh = () => {
  1257. if (data.users[item.id]) {
  1258. tc.innerHTML = `
  1259. <td class="c1">
  1260. <a href="/nuke.php?func=ucp&uid=${
  1261. item.id
  1262. }" class="b nobr">[${
  1263. item.name ? "@" + item.name : "#" + item.id
  1264. }]</a>
  1265. </td>
  1266. <td class="c2">
  1267. ${item.tags
  1268. .map((tag) => {
  1269. if (data.tags[tag]) {
  1270. return `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`;
  1271. }
  1272. })
  1273. .join("")}
  1274. </td>
  1275. <td class="c3">
  1276. <div class="filter-table-button-group">
  1277. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1278. </div>
  1279. </td>
  1280. <td class="c4">
  1281. <div class="filter-table-button-group">
  1282. <button>编辑</button>
  1283. <button>删除</button>
  1284. </div>
  1285. </td>
  1286. `;
  1287.  
  1288. const actions = tc.getElementsByTagName("button");
  1289.  
  1290. actions[0].onclick = () => {
  1291. data.users[item.id].filterMode = switchFilterMode(
  1292. data.users[item.id].filterMode || FILTER_MODE[0]
  1293. );
  1294.  
  1295. actions[0].innerHTML = data.users[item.id].filterMode;
  1296.  
  1297. saveData();
  1298. reFilter();
  1299. };
  1300.  
  1301. actions[1].onclick = () => {
  1302. editUser(item.id, item.name, tc.refresh);
  1303. };
  1304.  
  1305. actions[2].onclick = () => {
  1306. if (confirm("是否确认?")) {
  1307. delete data.users[item.id];
  1308. container.removeChild(tc);
  1309.  
  1310. saveData();
  1311. reFilter();
  1312. }
  1313. };
  1314. } else {
  1315. tc.remove();
  1316. }
  1317. };
  1318.  
  1319. tc.refresh();
  1320.  
  1321. container.appendChild(tc);
  1322. });
  1323. };
  1324.  
  1325. return func;
  1326. })();
  1327.  
  1328. return {
  1329. name: "用户",
  1330. content,
  1331. refresh,
  1332. };
  1333. })();
  1334.  
  1335. // 标记
  1336. const tagModule = (() => {
  1337. const content = (() => {
  1338. const c = document.createElement("div");
  1339.  
  1340. c.style = "display: none";
  1341. c.innerHTML = `
  1342. <div class="filter-table-wrapper">
  1343. <table class="filter-table forumbox">
  1344. <thead>
  1345. <tr class="block_txt_c0">
  1346. <th class="c1" width="1">标记</th>
  1347. <th class="c2">列表</th>
  1348. <th class="c3" width="1">过滤方式</th>
  1349. <th class="c4" width="1">操作</th>
  1350. </tr>
  1351. </thead>
  1352. <tbody></tbody>
  1353. </table>
  1354. </div>
  1355. `;
  1356.  
  1357. return c;
  1358. })();
  1359.  
  1360. const refresh = (() => {
  1361. const container = content.getElementsByTagName("tbody")[0];
  1362.  
  1363. const func = () => {
  1364. container.innerHTML = "";
  1365.  
  1366. Object.values(data.tags).forEach((item) => {
  1367. const tc = document.createElement("tr");
  1368.  
  1369. tc.className = `row${
  1370. (container.querySelectorAll("TR").length % 2) + 1
  1371. }`;
  1372.  
  1373. tc.innerHTML = `
  1374. <td class="c1">
  1375. <b class="block_txt nobr" style="background:${
  1376. item.color
  1377. }; color:#fff; margin: 0.1em 0.2em;">${item.name}</b>
  1378. </td>
  1379. <td class="c2">
  1380. <button>${
  1381. Object.values(data.users).filter((user) =>
  1382. user.tags.find((tag) => tag === item.id)
  1383. ).length
  1384. }
  1385. </button>
  1386. <div style="white-space: normal; display: none;">
  1387. ${Object.values(data.users)
  1388. .filter((user) =>
  1389. user.tags.find((tag) => tag === item.id)
  1390. )
  1391. .map(
  1392. (user) =>
  1393. `<a href="/nuke.php?func=ucp&uid=${
  1394. user.id
  1395. }" class="b nobr">[${
  1396. user.name ? "@" + user.name : "#" + user.id
  1397. }]</a>`
  1398. )
  1399. .join("")}
  1400. </div>
  1401. </td>
  1402. <td class="c3">
  1403. <div class="filter-table-button-group">
  1404. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1405. </div>
  1406. </td>
  1407. <td class="c4">
  1408. <div class="filter-table-button-group">
  1409. <button>删除</button>
  1410. </div>
  1411. </td>
  1412. `;
  1413.  
  1414. const actions = tc.getElementsByTagName("button");
  1415.  
  1416. actions[0].onclick = (() => {
  1417. let hide = true;
  1418. return () => {
  1419. hide = !hide;
  1420. actions[0].nextElementSibling.style.display = hide
  1421. ? "none"
  1422. : "block";
  1423. };
  1424. })();
  1425.  
  1426. actions[1].onclick = () => {
  1427. data.tags[item.id].filterMode = switchFilterMode(
  1428. data.tags[item.id].filterMode || FILTER_MODE[0]
  1429. );
  1430.  
  1431. actions[1].innerHTML = data.tags[item.id].filterMode;
  1432.  
  1433. saveData();
  1434. reFilter();
  1435. };
  1436.  
  1437. actions[2].onclick = () => {
  1438. if (confirm("是否确认?")) {
  1439. delete data.tags[item.id];
  1440.  
  1441. Object.values(data.users).forEach((user) => {
  1442. const index = user.tags.findIndex((tag) => tag === item.id);
  1443. if (index >= 0) {
  1444. user.tags.splice(index, 1);
  1445. }
  1446. });
  1447.  
  1448. container.removeChild(tc);
  1449.  
  1450. saveData();
  1451. reFilter();
  1452. }
  1453. };
  1454.  
  1455. container.appendChild(tc);
  1456. });
  1457. };
  1458.  
  1459. return func;
  1460. })();
  1461.  
  1462. return {
  1463. name: "标记",
  1464. content,
  1465. refresh,
  1466. };
  1467. })();
  1468.  
  1469. // 关键字
  1470. const keywordModule = (() => {
  1471. const content = (() => {
  1472. const c = document.createElement("div");
  1473.  
  1474. c.style = "display: none";
  1475. c.innerHTML = `
  1476. <div class="filter-table-wrapper">
  1477. <table class="filter-table forumbox">
  1478. <thead>
  1479. <tr class="block_txt_c0">
  1480. <th class="c1">列表</th>
  1481. <th class="c2" width="1">过滤方式</th>
  1482. <th class="c3" width="1">包括内容</th>
  1483. <th class="c4" width="1">操作</th>
  1484. </tr>
  1485. </thead>
  1486. <tbody></tbody>
  1487. </table>
  1488. </div>
  1489. <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用&quot;|&quot;隔开,&quot;ABC|DEF&quot;即为屏蔽带有ABC或者DEF的内容。</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.keywords).forEach((item) => {
  1502. const tc = document.createElement("tr");
  1503.  
  1504. tc.className = `row${
  1505. (container.querySelectorAll("TR").length % 2) + 1
  1506. }`;
  1507.  
  1508. tc.innerHTML = `
  1509. <td class="c1">
  1510. <div class="filter-input-wrapper">
  1511. <input value="${item.keyword || ""}" />
  1512. </div>
  1513. </td>
  1514. <td class="c2">
  1515. <div class="filter-table-button-group">
  1516. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1517. </div>
  1518. </td>
  1519. <td class="c3">
  1520. <div style="text-align: center;">
  1521. <input type="checkbox" ${
  1522. item.filterLevel ? `checked="checked"` : ""
  1523. } />
  1524. </div>
  1525. </td>
  1526. <td class="c4">
  1527. <div class="filter-table-button-group">
  1528. <button>保存</button>
  1529. <button>删除</button>
  1530. </div>
  1531. </td>
  1532. `;
  1533.  
  1534. const inputElement = tc.querySelector("INPUT");
  1535. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1536. const actions = tc.getElementsByTagName("button");
  1537.  
  1538. actions[0].onclick = () => {
  1539. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1540. };
  1541.  
  1542. actions[1].onclick = () => {
  1543. if (inputElement.value) {
  1544. data.keywords[item.id] = {
  1545. id: item.id,
  1546. keyword: inputElement.value,
  1547. filterMode: actions[0].innerHTML,
  1548. filterLevel: levelElement.checked ? 1 : 0,
  1549. };
  1550.  
  1551. saveData();
  1552. refresh();
  1553. }
  1554. };
  1555.  
  1556. actions[2].onclick = () => {
  1557. if (confirm("是否确认?")) {
  1558. delete data.keywords[item.id];
  1559.  
  1560. saveData();
  1561. refresh();
  1562. }
  1563. };
  1564.  
  1565. container.appendChild(tc);
  1566. });
  1567.  
  1568. {
  1569. const tc = document.createElement("tr");
  1570.  
  1571. tc.className = `row${
  1572. (container.querySelectorAll("TR").length % 2) + 1
  1573. }`;
  1574.  
  1575. tc.innerHTML = `
  1576. <td class="c1">
  1577. <div class="filter-input-wrapper">
  1578. <input value="" />
  1579. </div>
  1580. </td>
  1581. <td class="c2">
  1582. <div class="filter-table-button-group">
  1583. <button>${FILTER_MODE[0]}</button>
  1584. </div>
  1585. </td>
  1586. <td class="c3">
  1587. <div style="text-align: center;">
  1588. <input type="checkbox" />
  1589. </div>
  1590. </td>
  1591. <td class="c4">
  1592. <div class="filter-table-button-group">
  1593. <button>添加</button>
  1594. </div>
  1595. </td>
  1596. `;
  1597.  
  1598. const inputElement = tc.querySelector("INPUT");
  1599. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1600. const actions = tc.getElementsByTagName("button");
  1601.  
  1602. actions[0].onclick = () => {
  1603. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1604. };
  1605.  
  1606. actions[1].onclick = () => {
  1607. if (inputElement.value) {
  1608. addKeyword(
  1609. inputElement.value,
  1610. actions[0].innerHTML,
  1611. levelElement.checked ? 1 : 0
  1612. );
  1613.  
  1614. saveData();
  1615. refresh();
  1616. }
  1617. };
  1618.  
  1619. container.appendChild(tc);
  1620. }
  1621. };
  1622.  
  1623. return func;
  1624. })();
  1625.  
  1626. return {
  1627. name: "关键字",
  1628. content,
  1629. refresh,
  1630. };
  1631. })();
  1632.  
  1633. // 属地
  1634. const locationModule = (() => {
  1635. const content = (() => {
  1636. const c = document.createElement("div");
  1637.  
  1638. c.style = "display: none";
  1639. c.innerHTML = `
  1640. <div class="filter-table-wrapper">
  1641. <table class="filter-table forumbox">
  1642. <thead>
  1643. <tr class="block_txt_c0">
  1644. <th class="c1">列表</th>
  1645. <th class="c2" width="1">过滤方式</th>
  1646. <th class="c3" width="1">操作</th>
  1647. </tr>
  1648. </thead>
  1649. <tbody></tbody>
  1650. </table>
  1651. </div>
  1652. <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用&quot;|&quot;隔开,&quot;ABC|DEF&quot;即为屏蔽带有ABC或者DEF的内容。<br/>属地过滤功能需要占用额外的资源,请谨慎开启</div>
  1653. `;
  1654.  
  1655. return c;
  1656. })();
  1657.  
  1658. const refresh = (() => {
  1659. const container = content.getElementsByTagName("tbody")[0];
  1660.  
  1661. const func = () => {
  1662. container.innerHTML = "";
  1663.  
  1664. Object.values(data.locations).forEach((item) => {
  1665. const tc = document.createElement("tr");
  1666.  
  1667. tc.className = `row${
  1668. (container.querySelectorAll("TR").length % 2) + 1
  1669. }`;
  1670.  
  1671. tc.innerHTML = `
  1672. <td class="c1">
  1673. <div class="filter-input-wrapper">
  1674. <input value="${item.keyword || ""}" />
  1675. </div>
  1676. </td>
  1677. <td class="c2">
  1678. <div class="filter-table-button-group">
  1679. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1680. </div>
  1681. </td>
  1682. <td class="c3">
  1683. <div class="filter-table-button-group">
  1684. <button>保存</button>
  1685. <button>删除</button>
  1686. </div>
  1687. </td>
  1688. `;
  1689.  
  1690. const inputElement = tc.querySelector("INPUT");
  1691. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1692. const actions = tc.getElementsByTagName("button");
  1693.  
  1694. actions[0].onclick = () => {
  1695. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1696. };
  1697.  
  1698. actions[1].onclick = () => {
  1699. if (inputElement.value) {
  1700. data.locations[item.id] = {
  1701. id: item.id,
  1702. keyword: inputElement.value,
  1703. filterMode: actions[0].innerHTML,
  1704. };
  1705.  
  1706. saveData();
  1707. refresh();
  1708. }
  1709. };
  1710.  
  1711. actions[2].onclick = () => {
  1712. if (confirm("是否确认?")) {
  1713. delete data.locations[item.id];
  1714.  
  1715. saveData();
  1716. refresh();
  1717. }
  1718. };
  1719.  
  1720. container.appendChild(tc);
  1721. });
  1722.  
  1723. {
  1724. const tc = document.createElement("tr");
  1725.  
  1726. tc.className = `row${
  1727. (container.querySelectorAll("TR").length % 2) + 1
  1728. }`;
  1729.  
  1730. tc.innerHTML = `
  1731. <td class="c1">
  1732. <div class="filter-input-wrapper">
  1733. <input value="" />
  1734. </div>
  1735. </td>
  1736. <td class="c2">
  1737. <div class="filter-table-button-group">
  1738. <button>${FILTER_MODE[0]}</button>
  1739. </div>
  1740. </td>
  1741. <td class="c3">
  1742. <div class="filter-table-button-group">
  1743. <button>添加</button>
  1744. </div>
  1745. </td>
  1746. `;
  1747.  
  1748. const inputElement = tc.querySelector("INPUT");
  1749. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1750. const actions = tc.getElementsByTagName("button");
  1751.  
  1752. actions[0].onclick = () => {
  1753. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1754. };
  1755.  
  1756. actions[1].onclick = () => {
  1757. if (inputElement.value) {
  1758. addLocation(inputElement.value, actions[0].innerHTML);
  1759.  
  1760. saveData();
  1761. refresh();
  1762. }
  1763. };
  1764.  
  1765. container.appendChild(tc);
  1766. }
  1767. };
  1768.  
  1769. return func;
  1770. })();
  1771.  
  1772. return {
  1773. name: "属地",
  1774. content,
  1775. refresh,
  1776. };
  1777. })();
  1778.  
  1779. // 通用设置
  1780. const commonModule = (() => {
  1781. const content = (() => {
  1782. const c = document.createElement("div");
  1783.  
  1784. c.style = "display: none";
  1785.  
  1786. return c;
  1787. })();
  1788.  
  1789. const refresh = (() => {
  1790. const container = content;
  1791.  
  1792. const func = () => {
  1793. container.innerHTML = "";
  1794.  
  1795. // 默认过滤方式
  1796. {
  1797. const tc = document.createElement("div");
  1798.  
  1799. tc.innerHTML += `
  1800. <div>默认过滤方式</div>
  1801. <div></div>
  1802. <div class="silver" style="margin-top: 10px;">${FILTER_TIPS}</div>
  1803. `;
  1804.  
  1805. ["标记", "遮罩", "隐藏"].forEach((item, index) => {
  1806. const ele = document.createElement("SPAN");
  1807.  
  1808. ele.innerHTML += `
  1809. <input id="s-fm-${index}" type="radio" name="filterType" ${
  1810. data.options.filterMode === item && "checked"
  1811. }>
  1812. <label for="s-fm-${index}" style="cursor: pointer;">${item}</label>
  1813. `;
  1814.  
  1815. const inp = ele.querySelector("input");
  1816.  
  1817. inp.onchange = () => {
  1818. if (inp.checked) {
  1819. data.options.filterMode = item;
  1820. saveData();
  1821. reFilter();
  1822. }
  1823. };
  1824.  
  1825. tc.querySelectorAll("div")[1].append(ele);
  1826. });
  1827.  
  1828. container.appendChild(tc);
  1829. }
  1830.  
  1831. // 小号过滤(时间)
  1832. {
  1833. const tc = document.createElement("div");
  1834.  
  1835. tc.innerHTML += `
  1836. <br/>
  1837. <div>
  1838. 隐藏注册时间小于<input value="${
  1839. (data.options.filterRegdateLimit || 0) / 86400000
  1840. }" maxLength="4" style="width: 48px;" />天的用户
  1841. <button>确认</button>
  1842. </div>
  1843. `;
  1844.  
  1845. const actions = tc.getElementsByTagName("button");
  1846.  
  1847. actions[0].onclick = () => {
  1848. const v = actions[0].previousElementSibling.value;
  1849.  
  1850. const n = Number(v) || 0;
  1851.  
  1852. data.options.filterRegdateLimit = n < 0 ? 0 : n * 86400000;
  1853.  
  1854. saveData();
  1855. reFilter();
  1856. };
  1857.  
  1858. container.appendChild(tc);
  1859. }
  1860.  
  1861. // 小号过滤(发帖数)
  1862. {
  1863. const tc = document.createElement("div");
  1864.  
  1865. tc.innerHTML += `
  1866. <br/>
  1867. <div>
  1868. 隐藏发帖数量小于<input value="${
  1869. data.options.filterPostnumLimit || 0
  1870. }" maxLength="5" style="width: 48px;" />贴的用户
  1871. <button>确认</button>
  1872. </div>
  1873. `;
  1874.  
  1875. const actions = tc.getElementsByTagName("button");
  1876.  
  1877. actions[0].onclick = () => {
  1878. const v = actions[0].previousElementSibling.value;
  1879.  
  1880. const n = Number(v) || 0;
  1881.  
  1882. data.options.filterPostnumLimit = n < 0 ? 0 : n;
  1883.  
  1884. saveData();
  1885. reFilter();
  1886. };
  1887.  
  1888. container.appendChild(tc);
  1889. }
  1890.  
  1891. // 声望过滤
  1892. {
  1893. const tc = document.createElement("div");
  1894.  
  1895. tc.innerHTML += `
  1896. <br/>
  1897. <div>
  1898. 隐藏版面声望低于<input value="${
  1899. data.options.filterReputationLimit || ""
  1900. }" maxLength="5" style="width: 48px;" />点的用户
  1901. <button>确认</button>
  1902. </div>
  1903. `;
  1904.  
  1905. const actions = tc.getElementsByTagName("button");
  1906.  
  1907. actions[0].onclick = () => {
  1908. const v = actions[0].previousElementSibling.value;
  1909.  
  1910. const n = Number(v);
  1911.  
  1912. data.options.filterReputationLimit = n;
  1913.  
  1914. saveData();
  1915. reFilter();
  1916. };
  1917.  
  1918. container.appendChild(tc);
  1919. }
  1920.  
  1921. // 删除没有标记的用户
  1922. {
  1923. const tc = document.createElement("div");
  1924.  
  1925. tc.innerHTML += `
  1926. <br/>
  1927. <div>
  1928. <button>删除没有标记的用户</button>
  1929. </div>
  1930. `;
  1931.  
  1932. const actions = tc.getElementsByTagName("button");
  1933.  
  1934. actions[0].onclick = () => {
  1935. if (confirm("是否确认?")) {
  1936. Object.values(data.users).forEach((item) => {
  1937. if (item.tags.length === 0) {
  1938. delete data.users[item.id];
  1939. }
  1940. });
  1941.  
  1942. saveData();
  1943. reFilter();
  1944. }
  1945. };
  1946.  
  1947. container.appendChild(tc);
  1948. }
  1949.  
  1950. // 删除没有用户的标记
  1951. {
  1952. const tc = document.createElement("div");
  1953.  
  1954. tc.innerHTML += `
  1955. <br/>
  1956. <div>
  1957. <button>删除没有用户的标记</button>
  1958. </div>
  1959. `;
  1960.  
  1961. const actions = tc.getElementsByTagName("button");
  1962.  
  1963. actions[0].onclick = () => {
  1964. if (confirm("是否确认?")) {
  1965. Object.values(data.tags).forEach((item) => {
  1966. if (
  1967. Object.values(data.users).filter((user) =>
  1968. user.tags.find((tag) => tag === item.id)
  1969. ).length === 0
  1970. ) {
  1971. delete data.tags[item.id];
  1972. }
  1973. });
  1974.  
  1975. saveData();
  1976. reFilter();
  1977. }
  1978. };
  1979.  
  1980. container.appendChild(tc);
  1981. }
  1982. };
  1983.  
  1984. return func;
  1985. })();
  1986.  
  1987. return {
  1988. name: "通用设置",
  1989. content,
  1990. refresh,
  1991. };
  1992. })();
  1993.  
  1994. u.addModule(userModule).toggle();
  1995. u.addModule(tagModule);
  1996. u.addModule(keywordModule);
  1997. u.addModule(locationModule);
  1998. u.addModule(commonModule);
  1999.  
  2000. // 增加菜单项
  2001. (() => {
  2002. const title = "过滤设置";
  2003.  
  2004. let window;
  2005.  
  2006. const container = document.createElement("DIV");
  2007.  
  2008. container.className = `td`;
  2009. container.innerHTML = `<a class="mmdefault" href="javascript: void(0);" style="white-space: nowrap;">屏蔽</a>`;
  2010.  
  2011. const content = container.querySelector("A");
  2012.  
  2013. const anchor = document.querySelector("#mainmenu .td:last-child");
  2014.  
  2015. anchor.before(container);
  2016.  
  2017. content.onclick = () => {
  2018. if (window === undefined) {
  2019. window = n.createCommmonWindow();
  2020. }
  2021.  
  2022. window._.addContent(null);
  2023. window._.addTitle(title);
  2024. window._.addContent(u.content);
  2025. window._.show();
  2026. };
  2027. })();
  2028.  
  2029. // 执行过滤
  2030. (() => {
  2031. const hookFunction = (object, functionName, callback) => {
  2032. ((originalFunction) => {
  2033. object[functionName] = function () {
  2034. const returnValue = originalFunction.apply(this, arguments);
  2035.  
  2036. callback.apply(this, [returnValue, originalFunction, arguments]);
  2037.  
  2038. return returnValue;
  2039. };
  2040. })(object[functionName]);
  2041. };
  2042.  
  2043. const initialized = {
  2044. topicArg: false,
  2045. postArg: false,
  2046. };
  2047.  
  2048. hookFunction(n, "eval", () => {
  2049. if (Object.values(initialized).findIndex((item) => item === false) < 0) {
  2050. return;
  2051. }
  2052.  
  2053. if (n.topicArg && initialized.topicArg === false) {
  2054. hookFunction(n.topicArg, "add", reFilter);
  2055.  
  2056. initialized.topicArg = true;
  2057. }
  2058.  
  2059. if (n.postArg && initialized.postArg === false) {
  2060. hookFunction(n.postArg, "proc", reFilter);
  2061.  
  2062. initialized.postArg = true;
  2063. }
  2064. });
  2065.  
  2066. reFilter();
  2067. })();
  2068. })(commonui, __CURRENT_UID);