NGA Filter

troll must die

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

  1. // ==UserScript==
  2. // @name NGA Filter
  3. // @namespace https://greasyfork.org/users/263018
  4. // @version 1.1.0
  5. // @author snyssss
  6. // @description troll must die
  7.  
  8. // @match *bbs.nga.cn/thread.php?fid=*
  9. // @match *bbs.nga.cn/read.php?tid=*
  10. // @match *bbs.nga.cn/nuke.php?*
  11. // @match *ngabbs.com/thread.php?fid=*
  12. // @match *ngabbs.com/read.php?tid=*
  13. // @match *ngabbs.com/nuke.php?*
  14.  
  15. // @grant GM_addStyle
  16. // @grant GM_setValue
  17. // @grant GM_getValue
  18.  
  19. // @noframes
  20. // ==/UserScript==
  21.  
  22. ((n, self) => {
  23. if (n === undefined) return;
  24.  
  25. const key = "NGAFilter";
  26.  
  27. // 数据
  28. const data = (() => {
  29. const d = {
  30. tags: {},
  31. users: {},
  32. options: {
  33. filterMode: 0,
  34. keyword: "",
  35. },
  36. };
  37. const v = GM_getValue(key);
  38. if (typeof v !== "object") {
  39. return d;
  40. }
  41. return Object.assign(d, v);
  42. })();
  43.  
  44. // 保存数据
  45. const saveData = () => {
  46. GM_setValue(key, data);
  47. };
  48.  
  49. // 增加标记
  50. const addTag = (name) => {
  51. const tag = Object.values(data.tags).find((item) => item.name === name);
  52.  
  53. if (tag) return tag.id;
  54.  
  55. const id =
  56. Math.max(...Object.values(data.tags).map((item) => item.id), 0) + 1;
  57.  
  58. const hash = (() => {
  59. let h = 5381;
  60. for (var i = 0; i < name.length; i++) {
  61. h = ((h << 5) + h + name.charCodeAt(i)) & 0xffffffff;
  62. }
  63. return h;
  64. })();
  65.  
  66. const hex = Math.abs(hash).toString(16) + "000000";
  67.  
  68. const hsv = [
  69. `0x${hex.substr(2, 2)}` / 255,
  70. `0x${hex.substr(2, 2)}` / 255 / 2 + 0.25,
  71. `0x${hex.substr(4, 2)}` / 255 / 2 + 0.25,
  72. ];
  73.  
  74. const rgb = n.hsvToRgb(hsv[0], hsv[1], hsv[2]);
  75.  
  76. const color = ["#", ...rgb].reduce((a, b) => {
  77. return a + ("0" + b.toString(16)).slice(-2);
  78. });
  79.  
  80. data.tags[id] = {
  81. id,
  82. name,
  83. color,
  84. enabled: true,
  85. };
  86.  
  87. saveData();
  88.  
  89. return id;
  90. };
  91.  
  92. // 增加用户
  93. const addUser = (id, name = null, tags = [], isEnabled = true) => {
  94. if (data.users[id]) return data.users[id];
  95.  
  96. data.users[id] = {
  97. id,
  98. name,
  99. tags,
  100. enabled: isEnabled,
  101. };
  102.  
  103. saveData();
  104.  
  105. return data.users[id];
  106. };
  107.  
  108. // 旧版本数据迁移
  109. {
  110. const dataKey = "troll_data";
  111. const modeKey = "troll_mode";
  112. const keywordKey = "troll_keyword";
  113.  
  114. if (localStorage.getItem(dataKey)) {
  115. let trollMap = (function () {
  116. try {
  117. return JSON.parse(localStorage.getItem(dataKey)) || {};
  118. } catch (e) {}
  119.  
  120. return {};
  121. })();
  122.  
  123. let filterMode = ~~localStorage.getItem(modeKey);
  124.  
  125. let filterKeyword = localStorage.getItem(keywordKey) || "";
  126.  
  127. // 整理标签
  128. [...new Set(Object.values(trollMap).flat())].forEach((item) =>
  129. addTag(item)
  130. );
  131.  
  132. // 整理用户
  133. Object.keys(trollMap).forEach((item) => {
  134. addUser(
  135. item,
  136. null,
  137. (typeof trollMap[item] === "object"
  138. ? trollMap[item]
  139. : []
  140. ).map((tag) => addTag(tag))
  141. );
  142. });
  143.  
  144. data.options.filterMode = filterMode ? 0 : 1;
  145. data.options.keyword = filterKeyword;
  146.  
  147. localStorage.removeItem(dataKey);
  148. localStorage.removeItem(modeKey);
  149. localStorage.removeItem(keywordKey);
  150.  
  151. saveData();
  152. }
  153. }
  154.  
  155. // 编辑用户标记
  156. const editUser = (() => {
  157. let window;
  158. return (uid, name, callback) => {
  159. if (window === undefined) {
  160. window = n.createCommmonWindow();
  161. }
  162.  
  163. const user = data.users[uid];
  164.  
  165. const content = document.createElement("div");
  166.  
  167. const size = Math.floor((screen.width * 0.8) / 200);
  168.  
  169. const items = Object.values(data.tags).map(
  170. (tag, index) => `
  171. <td class="c1">
  172. <label for="s-tag-${index}" style="display: block; cursor: pointer;">
  173. <b class="block_txt nobr" style="background:${
  174. tag.color
  175. }; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>
  176. </label>
  177. </td>
  178. <td class="c2" width="1">
  179. <input id="s-tag-${index}" type="checkbox" value="${tag.id}" ${
  180. user && user.tags.find((item) => item === tag.id) && "checked"
  181. }/>
  182. </td>
  183. `
  184. );
  185.  
  186. const rows = [...new Array(Math.floor(items.length / size))].map(
  187. (item, index) =>
  188. `
  189. <tr class="row${(index % 2) + 1}">
  190. ${items.slice(size * index, size * (index + 1)).join("")}
  191. </tr>
  192. `
  193. );
  194.  
  195. content.className = "w100";
  196. content.innerHTML = `
  197. <div class="filter-table-wrapper" style="width: 80vw;">
  198. <table class="filter-table forumbox">
  199. <tbody>
  200. ${rows.join("")}
  201. </tbody>
  202. </table>
  203. </div>
  204. <div style="margin: 10px 0;">
  205. <input placeholder="一次性添加多个标记用&quot;|&quot;隔开,不会添加重名标记" style="width: -webkit-fill-available;" />
  206. </div>
  207. <div style="margin: 10px 0;">
  208. <button>${user && user.enabled === false ? "启用" : "禁用"}</button>
  209. <div class="right_">
  210. <button>删除</button>
  211. <button>保存</button>
  212. </div>
  213. </div>
  214. `;
  215.  
  216. const actions = content.getElementsByTagName("button");
  217.  
  218. actions[0].onclick = () => {
  219. actions[0].innerText =
  220. actions[0].innerText === "禁用" ? "启用" : "禁用";
  221. };
  222.  
  223. actions[1].onclick = () => {
  224. if (confirm("是否确认?")) {
  225. delete data.users[uid];
  226.  
  227. saveData();
  228.  
  229. callback && callback();
  230.  
  231. window._.hide();
  232. }
  233. };
  234.  
  235. actions[2].onclick = () => {
  236. if (confirm("是否确认?")) {
  237. const values = [...content.getElementsByTagName("input")];
  238. const newTags = values[values.length - 1].value
  239. .split("|")
  240. .filter((item) => item.length)
  241. .map((item) => addTag(item));
  242. const tags = [
  243. ...new Set(
  244. values
  245. .filter((item) => item.type === "checkbox" && item.checked)
  246. .map((item) => ~~item.value)
  247. .concat(newTags)
  248. ),
  249. ].sort();
  250.  
  251. if (user) {
  252. user.tags = tags;
  253. user.enabled = actions[0].innerText === "禁用";
  254. } else {
  255. addUser(uid, name, tags, actions[0].innerText === "禁用");
  256. }
  257.  
  258. saveData();
  259.  
  260. callback && callback();
  261.  
  262. window._.hide();
  263. }
  264. };
  265.  
  266. if (user === undefined) {
  267. actions[1].style = "display: none;";
  268. }
  269.  
  270. window._.addContent(null);
  271. window._.addTitle(`编辑标记 - ${name ? name : "#" + uid}`);
  272. window._.addContent(content);
  273. window._.show();
  274. };
  275. })();
  276.  
  277. // 过滤
  278. const reFilter = () => {
  279. const tPage = location.pathname === "/thread.php";
  280. const pPage = location.pathname === "/read.php";
  281. const uPage = location.pathname === "/nuke.php";
  282.  
  283. if (tPage) {
  284. const tData = n.topicArg.data;
  285.  
  286. Object.values(tData).forEach((item) => {
  287. if (item.containerC) return;
  288.  
  289. const uid =
  290. item[2].search.match(/uid=(\S+)/) &&
  291. item[2].search.match(/uid=(\S+)/)[1];
  292.  
  293. const user = data.users[uid];
  294.  
  295. const tags = user ? user.tags.map((tag) => data.tags[tag]) : [];
  296.  
  297. const isBlock =
  298. (user &&
  299. user.enabled &&
  300. (tags.length === 0 || tags.filter((tag) => tag.enabled).length)) ||
  301. (data.options.keyword.length &&
  302. item[1].innerText.search(data.options.keyword) >= 0);
  303.  
  304. item.contentC = item[1];
  305.  
  306. item.contentB = item.contentB || item.contentC.innerHTML;
  307.  
  308. item.containerC =
  309. item.containerC || item.contentC.parentNode.parentNode;
  310.  
  311. item.containerC.style =
  312. isBlock && data.options.filterMode === 0 ? "display: none;" : "";
  313.  
  314. item.contentC.style =
  315. isBlock && data.options.filterMode === 1
  316. ? "text-decoration: line-through;"
  317. : "";
  318. });
  319. } else if (pPage) {
  320. const pData = n.postArg.data;
  321.  
  322. Object.values(pData).forEach((item) => {
  323. if (~~item.pAid === self) return;
  324. if (item.containerC) return;
  325.  
  326. if (typeof item.i === "number") {
  327. item.actionC =
  328. item.actionC ||
  329. (() => {
  330. const ele = item.uInfoC.querySelector('[name="uid"]');
  331.  
  332. ele.onclick = null;
  333.  
  334. return ele;
  335. })();
  336.  
  337. item.tagC =
  338. item.tagC ||
  339. (() => {
  340. const tc = document.createElement("div");
  341.  
  342. tc.className = "filter-tags";
  343.  
  344. item.uInfoC.appendChild(tc);
  345.  
  346. return tc;
  347. })();
  348. }
  349.  
  350. item.pName =
  351. item.pName ||
  352. item.uInfoC.getElementsByClassName("author")[0].innerText;
  353.  
  354. item.reFilter =
  355. item.reFilter ||
  356. (() => {
  357. const user = data.users[item.pAid];
  358.  
  359. const tags = user ? user.tags.map((tag) => data.tags[tag]) : [];
  360.  
  361. const isBlock =
  362. user &&
  363. user.enabled &&
  364. (tags.length === 0 || tags.filter((tag) => tag.enabled).length);
  365.  
  366. item.avatarC =
  367. item.avatarC ||
  368. (() => {
  369. const tc = document.createElement("div");
  370.  
  371. const avatar = document.getElementById(`posteravatar${item.i}`);
  372.  
  373. if (avatar) {
  374. avatar.parentNode.insertBefore(tc, avatar.nextSibling);
  375.  
  376. tc.appendChild(avatar);
  377. }
  378.  
  379. return tc;
  380. })();
  381.  
  382. item.contentB = item.contentB || item.contentC.innerHTML;
  383.  
  384. item.containerC =
  385. item.containerC ||
  386. (() => {
  387. let temp = item.contentC;
  388.  
  389. while (
  390. temp.className !== "forumbox postbox" &&
  391. temp.className !== "comment_c left"
  392. ) {
  393. temp = temp.parentNode;
  394. }
  395.  
  396. return temp;
  397. })();
  398.  
  399. item.containerC.style.display =
  400. isBlock && data.options.filterMode === 0 ? "none" : "";
  401.  
  402. item.contentC.innerHTML =
  403. isBlock && data.options.filterMode === 1
  404. ? `
  405. <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7;">
  406. <span class="crimson">Troll must die.</span>
  407. <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${user.id}')].forEach(item => item.style.display = '')">点击查看</a>
  408. <div style="display: none;" name="troll_${user.id}">
  409. ${item.contentB}
  410. </div>
  411. </div>`
  412. : item.contentB;
  413.  
  414. item.avatarC.style.display = isBlock ? "none" : "";
  415.  
  416. if (item.actionC) {
  417. item.actionC.style =
  418. user && user.enabled
  419. ? "background: #cb4042;"
  420. : "background: #aaa;";
  421. }
  422.  
  423. if (item.tagC) {
  424. item.tagC.style.display = tags.length ? "" : "none";
  425. item.tagC.innerHTML = tags
  426. .map(
  427. (tag) =>
  428. `<b class="block_txt nobr" style="background:${tag.color}; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>`
  429. )
  430. .join("");
  431. }
  432. });
  433.  
  434. if (item.actionC) {
  435. item.actionC.onclick =
  436. item.actionC.onclick ||
  437. ((e) => {
  438. if (item.pAid < 0) return;
  439.  
  440. const user = data.users[item.pAid];
  441.  
  442. if (e.ctrlKey === false) {
  443. editUser(item.pAid, item.pName, item.reFilter);
  444. } else {
  445. if (user) {
  446. if (user.tags.length) {
  447. user.enabled = !user.enabled;
  448. user.name = item.pName;
  449. } else {
  450. delete data.users[user.id];
  451. }
  452. } else {
  453. addUser(item.pAid, item.pName);
  454. }
  455.  
  456. saveData();
  457. item.reFilter();
  458. }
  459. });
  460. }
  461.  
  462. item.reFilter();
  463. });
  464. } else if (uPage) {
  465. const container = document.getElementById("ucp_block");
  466.  
  467. if (container.firstChild) {
  468. const uid = container.innerText.match(/用户ID\s*:\s*(\S+)/)[1];
  469.  
  470. const name = container.innerText.match(/用户名\s*:\s*(\S+)/)[1];
  471.  
  472. container.tagC =
  473. container.tagC ||
  474. (() => {
  475. const c = document.createElement("span");
  476.  
  477. c.innerHTML = `
  478. <h2 class="catetitle">:: ${name} 的标记 ::</h2>
  479. <div class="cateblock" style="text-align: left; line-height: 1.8em;">
  480. <div class="contentBlock" style="padding: 5px 10px;">
  481. <span>
  482. <ul class="actions" style="padding: 0px; margin: 0px;">
  483. <li style="padding-right: 5px;">
  484. <span>
  485. <a href="javascript: void(0);">[编辑 ${name} 的标记]</a>
  486. </span>
  487. </li>
  488. <div class="clear"></div>
  489. </ul>
  490. </span>
  491. <div class="filter-tags"></div>
  492. <div class="clear"></div>
  493. </div>
  494. </div>
  495. `;
  496.  
  497. c.getElementsByTagName("a")[0].onclick = () => {
  498. editUser(uid, name, container.refresh);
  499. };
  500.  
  501. container.firstChild.insertBefore(
  502. c,
  503. container.firstChild.childNodes[1]
  504. );
  505.  
  506. return c.getElementsByClassName("filter-tags")[0];
  507. })();
  508.  
  509. container.refresh = () => {
  510. container.tagC.innerHTML = data.users[uid].tags
  511. .map(
  512. (tag) =>
  513. `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`
  514. )
  515. .join("");
  516. };
  517.  
  518. container.refresh();
  519. }
  520. }
  521. };
  522.  
  523. // STYLE
  524. GM_addStyle(`
  525. .filter-table-wrapper {
  526. max-height: 80vh;
  527. overflow-y: auto;
  528. }
  529. .filter-table {
  530. margin: 0;
  531. }
  532. .filter-table th,
  533. .filter-table td {
  534. position: relative;
  535. white-space: nowrap;
  536. }
  537. .filter-table th {
  538. position: sticky;
  539. top: 2px;
  540. z-index: 1;
  541. }
  542. .filter-table input:not([type]), .filter-table input[type="text"] {
  543. margin: 0;
  544. box-sizing: border-box;
  545. height: 100%;
  546. width: 100%;
  547. }
  548. .filter-input-wrapper {
  549. position: absolute;
  550. top: 6px;
  551. right: 6px;
  552. bottom: 6px;
  553. left: 6px;
  554. }
  555. .filter-text-ellipsis {
  556. display: flex;
  557. }
  558. .filter-text-ellipsis > * {
  559. flex: 1;
  560. width: 1px;
  561. overflow: hidden;
  562. text-overflow: ellipsis;
  563. }
  564. .filter-button-group {
  565. margin: -.1em -.2em;
  566. }
  567. .filter-tags {
  568. margin: 2px -0.2em 0;
  569. text-align: left;
  570. }
  571. `);
  572.  
  573. // UI
  574. const u = (() => {
  575. const modules = {};
  576.  
  577. const tabContainer = (() => {
  578. const c = document.createElement("div");
  579.  
  580. c.className = "w100";
  581. c.innerHTML = `
  582. <div class="right_" style="margin-bottom: 5px;">
  583. <table class="stdbtn" cellspacing="0">
  584. <tbody>
  585. <tr></tr>
  586. </tbody>
  587. </table>
  588. </div>
  589. <div class="clear"></div>
  590. `;
  591.  
  592. return c;
  593. })();
  594.  
  595. const tabPanelContainer = (() => {
  596. const c = document.createElement("div");
  597.  
  598. c.style = "width: 80vw;";
  599.  
  600. return c;
  601. })();
  602.  
  603. const content = (() => {
  604. const c = document.createElement("div");
  605.  
  606. c.append(tabContainer);
  607. c.append(tabPanelContainer);
  608.  
  609. return c;
  610. })();
  611.  
  612. const addModule = (() => {
  613. const tc = tabContainer.getElementsByTagName("tr")[0];
  614. const cc = tabPanelContainer;
  615.  
  616. return (module) => {
  617. const tabBox = document.createElement("td");
  618.  
  619. tabBox.innerHTML = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`;
  620.  
  621. const tab = tabBox.childNodes[0];
  622.  
  623. const toggle = () => {
  624. Object.values(modules).forEach((item) => {
  625. if (item.tab === tab) {
  626. item.tab.className = "nobr";
  627. item.content.style = "display: block";
  628. item.refresh();
  629. } else {
  630. item.tab.className = "nobr silver";
  631. item.content.style = "display: none";
  632. }
  633. });
  634. };
  635.  
  636. tc.append(tabBox);
  637. cc.append(module.content);
  638.  
  639. tab.onclick = toggle;
  640.  
  641. modules[module.name] = {
  642. ...module,
  643. tab,
  644. toggle,
  645. };
  646.  
  647. return modules[module.name];
  648. };
  649. })();
  650.  
  651. return {
  652. content,
  653. modules,
  654. addModule,
  655. };
  656. })();
  657.  
  658. // 屏蔽列表
  659. const blockModule = (() => {
  660. const content = (() => {
  661. const c = document.createElement("div");
  662.  
  663. c.style = "display: none";
  664. c.innerHTML = `
  665. <div class="filter-table-wrapper">
  666. <table class="filter-table forumbox">
  667. <thead>
  668. <tr class="block_txt_c0">
  669. <th class="c1" width="1">昵称</th>
  670. <th class="c2">标记</th>
  671. <th class="c3" width="1">操作</th>
  672. </tr>
  673. </thead>
  674. <tbody></tbody>
  675. </table>
  676. </div>
  677. `;
  678.  
  679. return c;
  680. })();
  681.  
  682. const refresh = (() => {
  683. const container = content.getElementsByTagName("tbody")[0];
  684.  
  685. const func = () => {
  686. container.innerHTML = "";
  687.  
  688. Object.values(data.users).forEach((item) => {
  689. const tc = document.createElement("tr");
  690.  
  691. tc.className = `row${
  692. (container.querySelectorAll("TR").length % 2) + 1
  693. }`;
  694.  
  695. tc.refresh = () => {
  696. if (data.users[item.id]) {
  697. tc.innerHTML = `
  698. <td class="c1">
  699. <a href="/nuke.php?func=ucp&uid=${
  700. item.id
  701. }" class="b nobr">[${
  702. item.name ? "@" + item.name : "#" + item.id
  703. }]</a>
  704. </td>
  705. <td class="c2">
  706. ${item.tags
  707. .map((tag) => {
  708. if (data.tags[tag]) {
  709. return `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`;
  710. }
  711. })
  712. .join("")}
  713. </td>
  714. <td class="c3">
  715. <div class="filter-table-button-group">
  716. <button>编辑</button>
  717. <button>${item.enabled ? "禁用" : "启用"}</button>
  718. <button>删除</button>
  719. </div>
  720. </td>
  721. `;
  722.  
  723. const actions = tc.getElementsByTagName("button");
  724.  
  725. actions[0].onclick = () => {
  726. editUser(item.id, item.name, tc.refresh);
  727. };
  728.  
  729. actions[1].onclick = () => {
  730. data.users[item.id].enabled = !data.users[item.id].enabled;
  731. actions[1].innerHTML = data.users[item.id].enabled
  732. ? "禁用"
  733. : "启用";
  734.  
  735. saveData();
  736. reFilter();
  737. };
  738.  
  739. actions[2].onclick = () => {
  740. if (confirm("是否确认?")) {
  741. delete data.users[item.id];
  742. container.removeChild(tc);
  743.  
  744. saveData();
  745. reFilter();
  746. }
  747. };
  748. } else {
  749. tc.remove();
  750. }
  751. };
  752.  
  753. tc.refresh();
  754.  
  755. container.appendChild(tc);
  756. });
  757. };
  758.  
  759. return func;
  760. })();
  761.  
  762. return {
  763. name: "屏蔽列表",
  764. content,
  765. refresh,
  766. };
  767. })();
  768.  
  769. // 标记设置
  770. const tagModule = (() => {
  771. const content = (() => {
  772. const c = document.createElement("div");
  773.  
  774. c.style = "display: none";
  775. c.innerHTML = `
  776. <div class="filter-table-wrapper">
  777. <table class="filter-table forumbox">
  778. <thead>
  779. <tr class="block_txt_c0">
  780. <th class="c1" width="1">标记</th>
  781. <th class="c2">列表</th>
  782. <th class="c3" width="1">操作</th>
  783. </tr>
  784. </thead>
  785. <tbody></tbody>
  786. </table>
  787. </div>
  788. `;
  789.  
  790. return c;
  791. })();
  792.  
  793. const refresh = (() => {
  794. const container = content.getElementsByTagName("tbody")[0];
  795.  
  796. const func = () => {
  797. container.innerHTML = "";
  798.  
  799. Object.values(data.tags).forEach((item) => {
  800. const tc = document.createElement("tr");
  801.  
  802. tc.className = `row${
  803. (container.querySelectorAll("TR").length % 2) + 1
  804. }`;
  805.  
  806. tc.innerHTML = `
  807. <td class="c1">
  808. <b class="block_txt nobr" style="background:${
  809. item.color
  810. }; color:#fff; margin: 0.1em 0.2em;">${item.name}</b>
  811. </td>
  812. <td class="c2">
  813. <button>${
  814. Object.values(data.users).filter((user) =>
  815. user.tags.find((tag) => tag === item.id)
  816. ).length
  817. }
  818. </button>
  819. <div style="white-space: normal; display: none;">
  820. ${Object.values(data.users)
  821. .filter((user) =>
  822. user.tags.find((tag) => tag === item.id)
  823. )
  824. .map(
  825. (user) =>
  826. `<a href="/nuke.php?func=ucp&uid=${
  827. user.id
  828. }" class="b nobr">[${
  829. user.name ? "@" + user.name : "#" + user.id
  830. }]</a>`
  831. )
  832. .join("")}
  833. </div>
  834. </td>
  835. <td class="c3">
  836. <div class="filter-table-button-group">
  837. <button>${item.enabled ? "禁用" : "启用"}</button>
  838. <button>删除</button>
  839. </div>
  840. </td>
  841. `;
  842.  
  843. const actions = tc.getElementsByTagName("button");
  844.  
  845. actions[0].onclick = (() => {
  846. let hide = true;
  847. return () => {
  848. hide = !hide;
  849. actions[0].nextElementSibling.style.display = hide
  850. ? "none"
  851. : "block";
  852. };
  853. })();
  854.  
  855. actions[1].onclick = () => {
  856. data.tags[item.id].enabled = !data.tags[item.id].enabled;
  857. actions[1].innerHTML = data.tags[item.id].enabled ? "禁用" : "启用";
  858.  
  859. saveData();
  860. reFilter();
  861. };
  862.  
  863. actions[2].onclick = () => {
  864. if (confirm("是否确认?")) {
  865. delete data.tags[item.id];
  866.  
  867. Object.values(data.users).forEach((user) => {
  868. const index = user.tags.findIndex((tag) => tag === item.id);
  869. if (index >= 0) {
  870. user.tags.splice(index, 1);
  871. }
  872. });
  873.  
  874. container.removeChild(tc);
  875.  
  876. saveData();
  877. reFilter();
  878. }
  879. };
  880.  
  881. container.appendChild(tc);
  882. });
  883. };
  884.  
  885. return func;
  886. })();
  887.  
  888. return {
  889. name: "标记设置",
  890. content,
  891. refresh,
  892. };
  893. })();
  894.  
  895. // 通用设置
  896. const commonModule = (() => {
  897. const content = (() => {
  898. const c = document.createElement("div");
  899.  
  900. c.style = "display: none";
  901.  
  902. return c;
  903. })();
  904.  
  905. const refresh = (() => {
  906. const container = content;
  907.  
  908. const func = () => {
  909. container.innerHTML = "";
  910.  
  911. // 屏蔽关键词
  912. {
  913. const tc = document.createElement("div");
  914.  
  915. tc.innerHTML += `
  916. <div>过滤关键词,用"|"隔开</div>
  917. <div>
  918. <textarea rows="3" style="box-sizing: border-box; width: 100%;">${data.options.keyword}</textarea>
  919. <button>确认</button>
  920. </div>
  921. `;
  922.  
  923. const actions = tc.getElementsByTagName("button");
  924.  
  925. actions[0].onclick = () => {
  926. const v = actions[0].previousElementSibling.value;
  927.  
  928. data.options.keyword = v;
  929.  
  930. saveData();
  931. reFilter();
  932. };
  933.  
  934. container.appendChild(tc);
  935. }
  936.  
  937. // 过滤方式
  938. {
  939. const tc = document.createElement("div");
  940.  
  941. tc.innerHTML += `
  942. <br/>
  943. <div>过滤方式</div>
  944. <div>
  945. <input type="radio" name="filterType" ${
  946. data.options.filterMode === 0 && "checked"
  947. }>
  948. <span>隐藏</span>
  949. <input type="radio" name="filterType" ${
  950. data.options.filterMode === 1 && "checked"
  951. }>
  952. <span>标记</span>
  953. <button>确认</button>
  954. </div>
  955. `;
  956.  
  957. const actions = tc.getElementsByTagName("button");
  958.  
  959. actions[0].onclick = () => {
  960. const values = document.getElementsByName("filterType");
  961.  
  962. for (let i = 0, length = values.length; i < length; i++) {
  963. if (values[i].checked) {
  964. data.options.filterMode = i;
  965. break;
  966. }
  967. }
  968.  
  969. saveData();
  970. reFilter();
  971. };
  972.  
  973. container.appendChild(tc);
  974. }
  975.  
  976. // 删除没有标记的用户
  977. {
  978. const tc = document.createElement("div");
  979.  
  980. tc.innerHTML += `
  981. <br/>
  982. <div>
  983. <button>删除没有标记的用户</button>
  984. </div>
  985. `;
  986.  
  987. const actions = tc.getElementsByTagName("button");
  988.  
  989. actions[0].onclick = () => {
  990. if (confirm("是否确认?")) {
  991. Object.values(data.users).forEach((item) => {
  992. if (item.tags.length === 0) {
  993. delete data.users[item.id];
  994. }
  995. });
  996.  
  997. saveData();
  998. reFilter();
  999. }
  1000. };
  1001.  
  1002. container.appendChild(tc);
  1003. }
  1004.  
  1005. // 删除没有用户的标记
  1006. {
  1007. const tc = document.createElement("div");
  1008.  
  1009. tc.innerHTML += `
  1010. <br/>
  1011. <div>
  1012. <button>删除没有用户的标记</button>
  1013. </div>
  1014. `;
  1015.  
  1016. const actions = tc.getElementsByTagName("button");
  1017.  
  1018. actions[0].onclick = () => {
  1019. if (confirm("是否确认?")) {
  1020. Object.values(data.tags).forEach((item) => {
  1021. if (
  1022. Object.values(data.users).filter((user) =>
  1023. user.tags.find((tag) => tag === item.id)
  1024. ).length === 0
  1025. ) {
  1026. delete data.tags[item.id];
  1027. }
  1028. });
  1029.  
  1030. saveData();
  1031. reFilter();
  1032. }
  1033. };
  1034.  
  1035. container.appendChild(tc);
  1036. }
  1037. };
  1038.  
  1039. return func;
  1040. })();
  1041.  
  1042. return {
  1043. name: "通用设置",
  1044. content,
  1045. refresh,
  1046. };
  1047. })();
  1048.  
  1049. u.addModule(blockModule).toggle();
  1050. u.addModule(tagModule);
  1051. u.addModule(commonModule);
  1052.  
  1053. // 增加菜单项
  1054. (() => {
  1055. const title = "屏蔽/标记";
  1056. let window;
  1057.  
  1058. n.mainMenu.addItemOnTheFly(title, null, () => {
  1059. if (window === undefined) {
  1060. window = n.createCommmonWindow();
  1061. }
  1062.  
  1063. window._.addContent(null);
  1064. window._.addTitle(title);
  1065. window._.addContent(u.content);
  1066. window._.show();
  1067. });
  1068. })();
  1069.  
  1070. // 执行过滤
  1071. (() => {
  1072. const hookFunction = (object, functionName, callback) => {
  1073. ((originalFunction) => {
  1074. object[functionName] = function () {
  1075. const returnValue = originalFunction.apply(this, arguments);
  1076.  
  1077. callback.apply(this, [returnValue, originalFunction, arguments]);
  1078.  
  1079. return returnValue;
  1080. };
  1081. })(object[functionName]);
  1082. };
  1083.  
  1084. const initialized = {
  1085. topicArg: false,
  1086. postArg: false,
  1087. };
  1088.  
  1089. hookFunction(n, "eval", () => {
  1090. if (Object.values(initialized).findIndex((item) => item === false) < 0) {
  1091. return;
  1092. }
  1093.  
  1094. if (n.topicArg && initialized.topicArg === false) {
  1095. hookFunction(
  1096. n.topicArg,
  1097. "add",
  1098. (returnValue, originalFunction, arguments) => reFilter()
  1099. );
  1100.  
  1101. initialized.topicArg = true;
  1102. }
  1103.  
  1104. if (n.postArg && initialized.postArg === false) {
  1105. hookFunction(
  1106. n.postArg,
  1107. "proc",
  1108. (returnValue, originalFunction, arguments) => reFilter()
  1109. );
  1110.  
  1111. initialized.postArg = true;
  1112. }
  1113. });
  1114.  
  1115. if (n.ucp) {
  1116. hookFunction(n.ucp, "_echo", (returnValue, originalFunction, arguments) =>
  1117. reFilter()
  1118. );
  1119. }
  1120.  
  1121. reFilter();
  1122. })();
  1123. })(commonui, __CURRENT_UID);