NGA Filter

troll must die

当前为 2021-04-09 提交的版本,查看 最新版本

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