NGA Filter

troll must die

当前为 2021-06-03 提交的版本,查看 最新版本

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