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