NGA Filter

troll must die

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

  1. // ==UserScript==
  2. // @name NGA Filter
  3. // @namespace https://greasyfork.org/users/263018
  4. // @version 1.2.2
  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.  
  477. item.containerC =
  478. item.containerC ||
  479. (() => {
  480. let temp = item.contentC;
  481.  
  482. if (item.i >= 0) {
  483. while (temp.nodeName !== "TBODY") {
  484. temp = temp.parentNode;
  485. }
  486. } else {
  487. while (temp.nodeName !== "DIV") {
  488. temp = temp.parentNode;
  489. }
  490. }
  491.  
  492. return temp;
  493. })();
  494.  
  495. item.avatarC.style.display = "";
  496. item.containerC.style.display = "";
  497. item.contentC.innerHTML = item.contentB;
  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. if (item.i >= 0) {
  519. caption.className = "filter-mask filter-mask-block";
  520. } else {
  521. caption.className = "filter-mask filter-mask-block left";
  522. caption.style = "width: 47%;";
  523. }
  524. caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
  525. caption.onclick = () => {
  526. item.containerC.parentNode.removeChild(caption);
  527. item.containerC.style.display = "";
  528. };
  529.  
  530. item.containerC.parentNode.insertBefore(caption, item.containerC);
  531. item.containerC.style.display = "none";
  532. if (item.actionC) {
  533. item.actionC.style = "background: #cb4042;";
  534. }
  535. } else if (filterMode === "隐藏") {
  536. item.containerC.style.display = "none";
  537. }
  538.  
  539. if (item.tagC) {
  540. item.tagC.style.display = tags.length ? "" : "none";
  541. item.tagC.innerHTML = tags
  542. .map(
  543. (tag) =>
  544. `<b class="block_txt nobr" style="background:${tag.color}; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>`
  545. )
  546. .join("");
  547. }
  548. });
  549.  
  550. if (item.actionC) {
  551. item.actionC.onclick =
  552. item.actionC.onclick ||
  553. ((e) => {
  554. if (item.pAid < 0) return;
  555.  
  556. const user = data.users[item.pAid];
  557.  
  558. if (e.ctrlKey === false) {
  559. editUser(item.pAid, item.pName, item.reFilter);
  560. } else {
  561. if (user) {
  562. delete data.users[user.id];
  563. } else {
  564. addUser(item.pAid, item.pName);
  565. }
  566.  
  567. saveData();
  568. item.reFilter();
  569. }
  570. });
  571. }
  572.  
  573. item.reFilter();
  574. });
  575. } else if (uPage) {
  576. const container = document.getElementById("ucp_block");
  577.  
  578. if (container.firstChild) {
  579. const uid = container.innerText.match(/用户ID\s*:\s*(\S+)/)[1];
  580.  
  581. const name = container.innerText.match(/用户名\s*:\s*(\S+)/)[1];
  582.  
  583. container.tagC =
  584. container.tagC ||
  585. (() => {
  586. const c = document.createElement("span");
  587.  
  588. c.innerHTML = `
  589. <h2 class="catetitle">:: ${name} 的标记 ::</h2>
  590. <div class="cateblock" style="text-align: left; line-height: 1.8em;">
  591. <div class="contentBlock" style="padding: 5px 10px;">
  592. <span>
  593. <ul class="actions" style="padding: 0px; margin: 0px;">
  594. <li style="padding-right: 5px;">
  595. <span>
  596. <a href="javascript: void(0);">[编辑 ${name} 的标记]</a>
  597. </span>
  598. </li>
  599. <div class="clear"></div>
  600. </ul>
  601. </span>
  602. <div class="filter-tags"></div>
  603. <div class="clear"></div>
  604. </div>
  605. </div>
  606. `;
  607.  
  608. c.getElementsByTagName("a")[0].onclick = () => {
  609. editUser(uid, name, container.refresh);
  610. };
  611.  
  612. container.firstChild.insertBefore(
  613. c,
  614. container.firstChild.childNodes[1]
  615. );
  616.  
  617. return c.getElementsByClassName("filter-tags")[0];
  618. })();
  619.  
  620. container.refresh = () => {
  621. container.tagC.innerHTML = data.users[uid].tags
  622. .map(
  623. (tag) =>
  624. `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`
  625. )
  626. .join("");
  627. };
  628.  
  629. container.refresh();
  630. }
  631. }
  632. };
  633.  
  634. // STYLE
  635. GM_addStyle(`
  636. .filter-table-wrapper {
  637. max-height: 80vh;
  638. overflow-y: auto;
  639. }
  640. .filter-table {
  641. margin: 0;
  642. }
  643. .filter-table th,
  644. .filter-table td {
  645. position: relative;
  646. white-space: nowrap;
  647. }
  648. .filter-table th {
  649. position: sticky;
  650. top: 2px;
  651. z-index: 1;
  652. }
  653. .filter-table input:not([type]), .filter-table input[type="text"] {
  654. margin: 0;
  655. box-sizing: border-box;
  656. height: 100%;
  657. width: 100%;
  658. }
  659. .filter-input-wrapper {
  660. position: absolute;
  661. top: 6px;
  662. right: 6px;
  663. bottom: 6px;
  664. left: 6px;
  665. }
  666. .filter-text-ellipsis {
  667. display: flex;
  668. }
  669. .filter-text-ellipsis > * {
  670. flex: 1;
  671. width: 1px;
  672. overflow: hidden;
  673. text-overflow: ellipsis;
  674. }
  675. .filter-button-group {
  676. margin: -.1em -.2em;
  677. }
  678. .filter-tags {
  679. margin: 2px -0.2em 0;
  680. text-align: left;
  681. }
  682. .filter-mask {
  683. margin: 1px;
  684. color: #81C7D4;
  685. background: #81C7D4;
  686. }
  687. .filter-mask-block {
  688. display: block;
  689. border: 1px solid #66BAB7;
  690. text-align: center !important;
  691. }
  692. `);
  693.  
  694. // UI
  695. const u = (() => {
  696. const modules = {};
  697.  
  698. const tabContainer = (() => {
  699. const c = document.createElement("div");
  700.  
  701. c.className = "w100";
  702. c.innerHTML = `
  703. <div class="right_" style="margin-bottom: 5px;">
  704. <table class="stdbtn" cellspacing="0">
  705. <tbody>
  706. <tr></tr>
  707. </tbody>
  708. </table>
  709. </div>
  710. <div class="clear"></div>
  711. `;
  712.  
  713. return c;
  714. })();
  715.  
  716. const tabPanelContainer = (() => {
  717. const c = document.createElement("div");
  718.  
  719. c.style = "width: 80vw;";
  720.  
  721. return c;
  722. })();
  723.  
  724. const content = (() => {
  725. const c = document.createElement("div");
  726.  
  727. c.append(tabContainer);
  728. c.append(tabPanelContainer);
  729.  
  730. return c;
  731. })();
  732.  
  733. const addModule = (() => {
  734. const tc = tabContainer.getElementsByTagName("tr")[0];
  735. const cc = tabPanelContainer;
  736.  
  737. return (module) => {
  738. const tabBox = document.createElement("td");
  739.  
  740. tabBox.innerHTML = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`;
  741.  
  742. const tab = tabBox.childNodes[0];
  743.  
  744. const toggle = () => {
  745. Object.values(modules).forEach((item) => {
  746. if (item.tab === tab) {
  747. item.tab.className = "nobr";
  748. item.content.style = "display: block";
  749. item.refresh();
  750. } else {
  751. item.tab.className = "nobr silver";
  752. item.content.style = "display: none";
  753. }
  754. });
  755. };
  756.  
  757. tc.append(tabBox);
  758. cc.append(module.content);
  759.  
  760. tab.onclick = toggle;
  761.  
  762. modules[module.name] = {
  763. ...module,
  764. tab,
  765. toggle,
  766. };
  767.  
  768. return modules[module.name];
  769. };
  770. })();
  771.  
  772. return {
  773. content,
  774. modules,
  775. addModule,
  776. };
  777. })();
  778.  
  779. // 屏蔽列表
  780. const blockModule = (() => {
  781. const content = (() => {
  782. const c = document.createElement("div");
  783.  
  784. c.style = "display: none";
  785. c.innerHTML = `
  786. <div class="filter-table-wrapper">
  787. <table class="filter-table forumbox">
  788. <thead>
  789. <tr class="block_txt_c0">
  790. <th class="c1" width="1">昵称</th>
  791. <th class="c2">标记</th>
  792. <th class="c3" width="1">过滤方式</th>
  793. <th class="c4" width="1">操作</th>
  794. </tr>
  795. </thead>
  796. <tbody></tbody>
  797. </table>
  798. </div>
  799. `;
  800.  
  801. return c;
  802. })();
  803.  
  804. const refresh = (() => {
  805. const container = content.getElementsByTagName("tbody")[0];
  806.  
  807. const func = () => {
  808. container.innerHTML = "";
  809.  
  810. Object.values(data.users).forEach((item) => {
  811. const tc = document.createElement("tr");
  812.  
  813. tc.className = `row${
  814. (container.querySelectorAll("TR").length % 2) + 1
  815. }`;
  816.  
  817. tc.refresh = () => {
  818. if (data.users[item.id]) {
  819. tc.innerHTML = `
  820. <td class="c1">
  821. <a href="/nuke.php?func=ucp&uid=${
  822. item.id
  823. }" class="b nobr">[${
  824. item.name ? "@" + item.name : "#" + item.id
  825. }]</a>
  826. </td>
  827. <td class="c2">
  828. ${item.tags
  829. .map((tag) => {
  830. if (data.tags[tag]) {
  831. return `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`;
  832. }
  833. })
  834. .join("")}
  835. </td>
  836. <td class="c3">
  837. <div class="filter-table-button-group">
  838. <button>${item.filterMode || FILTER_MODE[0]}</button>
  839. </div>
  840. </td>
  841. <td class="c4">
  842. <div class="filter-table-button-group">
  843. <button>编辑</button>
  844. <button>删除</button>
  845. </div>
  846. </td>
  847. `;
  848.  
  849. const actions = tc.getElementsByTagName("button");
  850.  
  851. actions[0].onclick = () => {
  852. data.users[item.id].filterMode = switchFilterMode(
  853. data.users[item.id].filterMode || FILTER_MODE[0]
  854. );
  855.  
  856. actions[0].innerHTML = data.users[item.id].filterMode;
  857.  
  858. saveData();
  859. reFilter();
  860. };
  861.  
  862. actions[1].onclick = () => {
  863. editUser(item.id, item.name, tc.refresh);
  864. };
  865.  
  866. actions[2].onclick = () => {
  867. if (confirm("是否确认?")) {
  868. delete data.users[item.id];
  869. container.removeChild(tc);
  870.  
  871. saveData();
  872. reFilter();
  873. }
  874. };
  875. } else {
  876. tc.remove();
  877. }
  878. };
  879.  
  880. tc.refresh();
  881.  
  882. container.appendChild(tc);
  883. });
  884. };
  885.  
  886. return func;
  887. })();
  888.  
  889. return {
  890. name: "屏蔽列表",
  891. content,
  892. refresh,
  893. };
  894. })();
  895.  
  896. // 标记设置
  897. const tagModule = (() => {
  898. const content = (() => {
  899. const c = document.createElement("div");
  900.  
  901. c.style = "display: none";
  902. c.innerHTML = `
  903. <div class="filter-table-wrapper">
  904. <table class="filter-table forumbox">
  905. <thead>
  906. <tr class="block_txt_c0">
  907. <th class="c1" width="1">标记</th>
  908. <th class="c2">列表</th>
  909. <th class="c3" width="1">过滤方式</th>
  910. <th class="c4" width="1">操作</th>
  911. </tr>
  912. </thead>
  913. <tbody></tbody>
  914. </table>
  915. </div>
  916. `;
  917.  
  918. return c;
  919. })();
  920.  
  921. const refresh = (() => {
  922. const container = content.getElementsByTagName("tbody")[0];
  923.  
  924. const func = () => {
  925. container.innerHTML = "";
  926.  
  927. Object.values(data.tags).forEach((item) => {
  928. const tc = document.createElement("tr");
  929.  
  930. tc.className = `row${
  931. (container.querySelectorAll("TR").length % 2) + 1
  932. }`;
  933.  
  934. tc.innerHTML = `
  935. <td class="c1">
  936. <b class="block_txt nobr" style="background:${
  937. item.color
  938. }; color:#fff; margin: 0.1em 0.2em;">${item.name}</b>
  939. </td>
  940. <td class="c2">
  941. <button>${
  942. Object.values(data.users).filter((user) =>
  943. user.tags.find((tag) => tag === item.id)
  944. ).length
  945. }
  946. </button>
  947. <div style="white-space: normal; display: none;">
  948. ${Object.values(data.users)
  949. .filter((user) =>
  950. user.tags.find((tag) => tag === item.id)
  951. )
  952. .map(
  953. (user) =>
  954. `<a href="/nuke.php?func=ucp&uid=${
  955. user.id
  956. }" class="b nobr">[${
  957. user.name ? "@" + user.name : "#" + user.id
  958. }]</a>`
  959. )
  960. .join("")}
  961. </div>
  962. </td>
  963. <td class="c3">
  964. <div class="filter-table-button-group">
  965. <button>${item.filterMode || FILTER_MODE[0]}</button>
  966. </div>
  967. </td>
  968. <td class="c3">
  969. <div class="filter-table-button-group">
  970. <button>删除</button>
  971. </div>
  972. </td>
  973. `;
  974.  
  975. const actions = tc.getElementsByTagName("button");
  976.  
  977. actions[0].onclick = (() => {
  978. let hide = true;
  979. return () => {
  980. hide = !hide;
  981. actions[0].nextElementSibling.style.display = hide
  982. ? "none"
  983. : "block";
  984. };
  985. })();
  986.  
  987. actions[1].onclick = () => {
  988. data.tags[item.id].filterMode = switchFilterMode(
  989. data.tags[item.id].filterMode || FILTER_MODE[0]
  990. );
  991.  
  992. actions[1].innerHTML = data.tags[item.id].filterMode;
  993.  
  994. saveData();
  995. reFilter();
  996. };
  997.  
  998. actions[2].onclick = () => {
  999. if (confirm("是否确认?")) {
  1000. delete data.tags[item.id];
  1001.  
  1002. Object.values(data.users).forEach((user) => {
  1003. const index = user.tags.findIndex((tag) => tag === item.id);
  1004. if (index >= 0) {
  1005. user.tags.splice(index, 1);
  1006. }
  1007. });
  1008.  
  1009. container.removeChild(tc);
  1010.  
  1011. saveData();
  1012. reFilter();
  1013. }
  1014. };
  1015.  
  1016. container.appendChild(tc);
  1017. });
  1018. };
  1019.  
  1020. return func;
  1021. })();
  1022.  
  1023. return {
  1024. name: "标记设置",
  1025. content,
  1026. refresh,
  1027. };
  1028. })();
  1029.  
  1030. // 通用设置
  1031. const commonModule = (() => {
  1032. const content = (() => {
  1033. const c = document.createElement("div");
  1034.  
  1035. c.style = "display: none";
  1036.  
  1037. return c;
  1038. })();
  1039.  
  1040. const refresh = (() => {
  1041. const container = content;
  1042.  
  1043. const func = () => {
  1044. container.innerHTML = "";
  1045.  
  1046. // 屏蔽关键词
  1047. {
  1048. const tc = document.createElement("div");
  1049.  
  1050. tc.innerHTML += `
  1051. <div>过滤关键词,用"|"隔开</div>
  1052. <div>
  1053. <textarea rows="3" style="box-sizing: border-box; width: 100%;">${data.options.keyword}</textarea>
  1054. <button>确认</button>
  1055. </div>
  1056. `;
  1057.  
  1058. const actions = tc.getElementsByTagName("button");
  1059.  
  1060. actions[0].onclick = () => {
  1061. const v = actions[0].previousElementSibling.value;
  1062.  
  1063. data.options.keyword = v;
  1064.  
  1065. saveData();
  1066. reFilter();
  1067. };
  1068.  
  1069. container.appendChild(tc);
  1070. }
  1071.  
  1072. // 全局过滤方式
  1073. {
  1074. const tc = document.createElement("div");
  1075.  
  1076. tc.innerHTML += `
  1077. <br/>
  1078. <div>全局过滤方式</div>
  1079. <div></div>
  1080. <div class="silver" style="margin-top: 5px;">过滤优先级:用户 &gt; 标记 &gt; 全局</div>
  1081. `;
  1082.  
  1083. ["标记", "遮罩", "隐藏"].forEach((item, index) => {
  1084. const ele = document.createElement("SPAN");
  1085.  
  1086. ele.innerHTML += `
  1087. <input id="s-fm-${index}" type="radio" name="filterType" ${
  1088. data.options.filterMode === item && "checked"
  1089. }>
  1090. <label for="s-fm-${index}" style="cursor: pointer;">${item}</label>
  1091. `;
  1092.  
  1093. const inp = ele.querySelector("input");
  1094.  
  1095. inp.onchange = () => {
  1096. if (inp.checked) {
  1097. data.options.filterMode = item;
  1098. saveData();
  1099. reFilter();
  1100. }
  1101. };
  1102.  
  1103. tc.querySelectorAll("div")[1].append(ele);
  1104. });
  1105.  
  1106. container.appendChild(tc);
  1107. }
  1108.  
  1109. // 删除没有标记的用户
  1110. {
  1111. const tc = document.createElement("div");
  1112.  
  1113. tc.innerHTML += `
  1114. <br/>
  1115. <div>
  1116. <button>删除没有标记的用户</button>
  1117. </div>
  1118. `;
  1119.  
  1120. const actions = tc.getElementsByTagName("button");
  1121.  
  1122. actions[0].onclick = () => {
  1123. if (confirm("是否确认?")) {
  1124. Object.values(data.users).forEach((item) => {
  1125. if (item.tags.length === 0) {
  1126. delete data.users[item.id];
  1127. }
  1128. });
  1129.  
  1130. saveData();
  1131. reFilter();
  1132. }
  1133. };
  1134.  
  1135. container.appendChild(tc);
  1136. }
  1137.  
  1138. // 删除没有用户的标记
  1139. {
  1140. const tc = document.createElement("div");
  1141.  
  1142. tc.innerHTML += `
  1143. <br/>
  1144. <div>
  1145. <button>删除没有用户的标记</button>
  1146. </div>
  1147. `;
  1148.  
  1149. const actions = tc.getElementsByTagName("button");
  1150.  
  1151. actions[0].onclick = () => {
  1152. if (confirm("是否确认?")) {
  1153. Object.values(data.tags).forEach((item) => {
  1154. if (
  1155. Object.values(data.users).filter((user) =>
  1156. user.tags.find((tag) => tag === item.id)
  1157. ).length === 0
  1158. ) {
  1159. delete data.tags[item.id];
  1160. }
  1161. });
  1162.  
  1163. saveData();
  1164. reFilter();
  1165. }
  1166. };
  1167.  
  1168. container.appendChild(tc);
  1169. }
  1170. };
  1171.  
  1172. return func;
  1173. })();
  1174.  
  1175. return {
  1176. name: "通用设置",
  1177. content,
  1178. refresh,
  1179. };
  1180. })();
  1181.  
  1182. u.addModule(blockModule).toggle();
  1183. u.addModule(tagModule);
  1184. u.addModule(commonModule);
  1185.  
  1186. // 增加菜单项
  1187. (() => {
  1188. const title = "屏蔽/标记";
  1189. let window;
  1190.  
  1191. n.mainMenu.addItemOnTheFly(title, null, () => {
  1192. if (window === undefined) {
  1193. window = n.createCommmonWindow();
  1194. }
  1195.  
  1196. window._.addContent(null);
  1197. window._.addTitle(title);
  1198. window._.addContent(u.content);
  1199. window._.show();
  1200. });
  1201. })();
  1202.  
  1203. // 执行过滤
  1204. (() => {
  1205. const hookFunction = (object, functionName, callback) => {
  1206. ((originalFunction) => {
  1207. object[functionName] = function () {
  1208. const returnValue = originalFunction.apply(this, arguments);
  1209.  
  1210. callback.apply(this, [returnValue, originalFunction, arguments]);
  1211.  
  1212. return returnValue;
  1213. };
  1214. })(object[functionName]);
  1215. };
  1216.  
  1217. const initialized = {
  1218. topicArg: false,
  1219. postArg: false,
  1220. };
  1221.  
  1222. hookFunction(n, "eval", () => {
  1223. if (Object.values(initialized).findIndex((item) => item === false) < 0) {
  1224. return;
  1225. }
  1226.  
  1227. if (n.topicArg && initialized.topicArg === false) {
  1228. hookFunction(
  1229. n.topicArg,
  1230. "add",
  1231. (returnValue, originalFunction, arguments) => reFilter()
  1232. );
  1233.  
  1234. initialized.topicArg = true;
  1235. }
  1236.  
  1237. if (n.postArg && initialized.postArg === false) {
  1238. hookFunction(
  1239. n.postArg,
  1240. "proc",
  1241. (returnValue, originalFunction, arguments) => reFilter()
  1242. );
  1243.  
  1244. initialized.postArg = true;
  1245. }
  1246. });
  1247.  
  1248. if (n.ucp) {
  1249. hookFunction(n.ucp, "_echo", (returnValue, originalFunction, arguments) =>
  1250. reFilter()
  1251. );
  1252. }
  1253.  
  1254. reFilter();
  1255. })();
  1256. })(commonui, __CURRENT_UID);