NGA Filter

troll must die

当前为 2021-05-27 提交的版本,查看 最新版本

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