Greasy Fork 还支持 简体中文。

NGA Filter

troll must die

目前為 2021-06-18 提交的版本,檢視 最新版本

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