NGA Filter

troll must die

当前为 2022-09-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name NGA Filter
  3. // @namespace https://greasyfork.org/users/263018
  4. // @version 1.9.0
  5. // @author snyssss
  6. // @description troll must die
  7. // @license MIT
  8.  
  9. // @match *://bbs.nga.cn/*
  10. // @match *://ngabbs.com/*
  11. // @match *://nga.178.com/*
  12.  
  13. // @grant GM_addStyle
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16.  
  17. // @noframes
  18. // ==/UserScript==
  19.  
  20. ((n, self) => {
  21. if (n === undefined) return;
  22.  
  23. const key = "NGAFilter";
  24.  
  25. // 过滤提示
  26. const FILTER_TIPS =
  27. "过滤顺序:用户 &gt; 标记 &gt; 关键字 &gt; 属地<br/>过滤级别:隐藏 &gt; 遮罩 &gt; 标记 &gt; 继承 &gt; 显示<br/>相同类型按最高级别过滤";
  28.  
  29. // 过滤方式
  30. const FILTER_MODE = ["继承", "标记", "遮罩", "隐藏", "显示"];
  31.  
  32. // 切换过滤方式
  33. const switchFilterMode = (value) => {
  34. const next = FILTER_MODE.indexOf(value) + 1;
  35.  
  36. if (next >= FILTER_MODE.length) {
  37. return FILTER_MODE[0];
  38. }
  39.  
  40. return FILTER_MODE[next];
  41. };
  42.  
  43. // 数据
  44. const data = (() => {
  45. const d = {
  46. tags: {},
  47. users: {},
  48. keywords: {},
  49. locations: {},
  50. options: {
  51. filterRegdateLimit: 0,
  52. filterPostnumLimit: 0,
  53. filterReputationLimit: NaN,
  54. filterMode: "隐藏",
  55. },
  56. };
  57.  
  58. const v = GM_getValue(key);
  59.  
  60. if (typeof v !== "object") {
  61. return d;
  62. }
  63.  
  64. return Object.assign(d, v);
  65. })();
  66.  
  67. // 保存数据
  68. const saveData = () => {
  69. GM_setValue(key, data);
  70. };
  71.  
  72. // 增加标记
  73. const addTag = (name) => {
  74. const tag = Object.values(data.tags).find((item) => item.name === name);
  75.  
  76. if (tag) return tag.id;
  77.  
  78. const id =
  79. Math.max(...Object.values(data.tags).map((item) => item.id), 0) + 1;
  80.  
  81. const hash = (() => {
  82. let h = 5381;
  83. for (var i = 0; i < name.length; i++) {
  84. h = ((h << 5) + h + name.charCodeAt(i)) & 0xffffffff;
  85. }
  86. return h;
  87. })();
  88.  
  89. const hex = Math.abs(hash).toString(16) + "000000";
  90.  
  91. const hsv = [
  92. `0x${hex.substring(2, 4)}` / 255,
  93. `0x${hex.substring(2, 4)}` / 255 / 2 + 0.25,
  94. `0x${hex.substring(4, 6)}` / 255 / 2 + 0.25,
  95. ];
  96.  
  97. const rgb = n.hsvToRgb(hsv[0], hsv[1], hsv[2]);
  98.  
  99. const color = ["#", ...rgb].reduce((a, b) => {
  100. return a + ("0" + b.toString(16)).slice(-2);
  101. });
  102.  
  103. data.tags[id] = {
  104. id,
  105. name,
  106. color,
  107. filterMode: FILTER_MODE[0],
  108. };
  109.  
  110. saveData();
  111.  
  112. return id;
  113. };
  114.  
  115. // 增加用户
  116. const addUser = (id, name = null, tags = [], filterMode = FILTER_MODE[0]) => {
  117. if (data.users[id]) return data.users[id];
  118.  
  119. data.users[id] = {
  120. id,
  121. name,
  122. tags,
  123. filterMode,
  124. };
  125.  
  126. saveData();
  127.  
  128. return data.users[id];
  129. };
  130.  
  131. // 增加关键字
  132. const addKeyword = (
  133. keyword,
  134. filterMode = FILTER_MODE[0],
  135. filterLevel = 0
  136. ) => {
  137. const id =
  138. Math.max(...Object.values(data.keywords).map((item) => item.id), 0) + 1;
  139.  
  140. data.keywords[id] = {
  141. id,
  142. keyword,
  143. filterMode,
  144. filterLevel,
  145. };
  146.  
  147. saveData();
  148.  
  149. return id;
  150. };
  151.  
  152. // 增加属地
  153. const addLocation = (keyword, filterMode = FILTER_MODE[0]) => {
  154. const id =
  155. Math.max(...Object.values(data.locations).map((item) => item.id), 0) + 1;
  156.  
  157. data.locations[id] = {
  158. id,
  159. keyword,
  160. filterMode,
  161. };
  162.  
  163. saveData();
  164.  
  165. return id;
  166. };
  167.  
  168. // 旧版本数据迁移
  169. {
  170. const dataKey = "troll_data";
  171. const modeKey = "troll_mode";
  172. const keywordKey = "troll_keyword";
  173.  
  174. if (localStorage.getItem(dataKey)) {
  175. let trollMap = (function () {
  176. try {
  177. return JSON.parse(localStorage.getItem(dataKey)) || {};
  178. } catch (e) {}
  179.  
  180. return {};
  181. })();
  182.  
  183. let filterMode = ~~localStorage.getItem(modeKey);
  184.  
  185. let filterKeyword = localStorage.getItem(keywordKey) || "";
  186.  
  187. // 整理标签
  188. [...new Set(Object.values(trollMap).flat())].forEach((item) =>
  189. addTag(item)
  190. );
  191.  
  192. // 整理用户
  193. Object.keys(trollMap).forEach((item) => {
  194. addUser(
  195. item,
  196. null,
  197. (typeof trollMap[item] === "object" ? trollMap[item] : []).map(
  198. (tag) => addTag(tag)
  199. )
  200. );
  201. });
  202.  
  203. data.options.filterMode = filterMode ? "隐藏" : "标记";
  204. data.options.keyword = filterKeyword;
  205.  
  206. localStorage.removeItem(dataKey);
  207. localStorage.removeItem(modeKey);
  208. localStorage.removeItem(keywordKey);
  209.  
  210. saveData();
  211. }
  212.  
  213. // v1.1.0 -> v1.1.1
  214. {
  215. Object.values(data.users).forEach(({ id, name, tags, enabled }) => {
  216. if (enabled !== undefined) {
  217. data.users[id] = {
  218. id,
  219. name,
  220. tags,
  221. filterMode: enabled ? "继承" : "显示",
  222. };
  223. }
  224. });
  225.  
  226. Object.values(data.tags).forEach(({ id, name, color, enabled }) => {
  227. if (enabled !== undefined) {
  228. data.tags[id] = {
  229. id,
  230. name,
  231. color,
  232. filterMode: enabled ? "继承" : "显示",
  233. };
  234. }
  235. });
  236.  
  237. if (data.options.filterMode === 0) {
  238. data.options.filterMode = "隐藏";
  239. } else if (data.options.filterMode === 1) {
  240. data.options.filterMode = "标记";
  241. }
  242.  
  243. saveData();
  244. }
  245.  
  246. // v1.2.x -> v1.3.0
  247. {
  248. if (data.options.keyword) {
  249. addKeyword(data.options.keyword);
  250.  
  251. delete data.options.keyword;
  252.  
  253. saveData();
  254. }
  255. }
  256. }
  257.  
  258. // 编辑用户标记
  259. const editUser = (() => {
  260. let window;
  261. return (uid, name, callback) => {
  262. if (window === undefined) {
  263. window = n.createCommmonWindow();
  264. }
  265.  
  266. const user = data.users[uid];
  267.  
  268. const content = document.createElement("div");
  269.  
  270. const size = Math.floor((screen.width * 0.8) / 200);
  271.  
  272. const items = Object.values(data.tags).map(
  273. (tag, index) => `
  274. <td class="c1">
  275. <label for="s-tag-${index}" style="display: block; cursor: pointer;">
  276. <b class="block_txt nobr" style="background:${
  277. tag.color
  278. }; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>
  279. </label>
  280. </td>
  281. <td class="c2" width="1">
  282. <input id="s-tag-${index}" type="checkbox" value="${tag.id}" ${
  283. user && user.tags.find((item) => item === tag.id) && "checked"
  284. }/>
  285. </td>
  286. `
  287. );
  288.  
  289. const rows = [...new Array(Math.ceil(items.length / size))].map(
  290. (item, index) =>
  291. `
  292. <tr class="row${(index % 2) + 1}">
  293. ${items.slice(size * index, size * (index + 1)).join("")}
  294. </tr>
  295. `
  296. );
  297.  
  298. content.className = "w100";
  299. content.innerHTML = `
  300. <div class="filter-table-wrapper" style="width: 80vw;">
  301. <table class="filter-table forumbox">
  302. <tbody>
  303. ${rows.join("")}
  304. </tbody>
  305. </table>
  306. </div>
  307. <div style="margin: 10px 0;">
  308. <input placeholder="一次性添加多个标记用&quot;|&quot;隔开,不会添加重名标记" style="width: -webkit-fill-available;" />
  309. </div>
  310. <div style="margin: 10px 0;">
  311. <span>过滤方式:</span>
  312. <button>${(user && user.filterMode) || FILTER_MODE[0]}</button>
  313. <div class="right_">
  314. <button>删除</button>
  315. <button>保存</button>
  316. </div>
  317. </div>
  318. <div class="silver" style="margin-top: 5px;">${FILTER_TIPS}</div>
  319. `;
  320.  
  321. const actions = content.getElementsByTagName("button");
  322.  
  323. actions[0].onclick = () => {
  324. actions[0].innerText = switchFilterMode(
  325. actions[0].innerText || FILTER_MODE[0]
  326. );
  327. };
  328.  
  329. actions[1].onclick = () => {
  330. if (confirm("是否确认?")) {
  331. delete data.users[uid];
  332.  
  333. saveData();
  334.  
  335. callback && callback();
  336.  
  337. window._.hide();
  338. }
  339. };
  340.  
  341. actions[2].onclick = () => {
  342. if (confirm("是否确认?")) {
  343. const values = [...content.getElementsByTagName("input")];
  344. const newTags = values[values.length - 1].value
  345. .split("|")
  346. .filter((item) => item.length)
  347. .map((item) => addTag(item));
  348. const tags = [
  349. ...new Set(
  350. values
  351. .filter((item) => item.type === "checkbox" && item.checked)
  352. .map((item) => ~~item.value)
  353. .concat(newTags)
  354. ),
  355. ].sort();
  356.  
  357. if (user) {
  358. user.tags = tags;
  359. user.filterMode = actions[0].innerText;
  360. } else {
  361. addUser(uid, name, tags, actions[0].innerText);
  362. }
  363.  
  364. saveData();
  365.  
  366. callback && callback();
  367.  
  368. window._.hide();
  369. }
  370. };
  371.  
  372. if (user === undefined) {
  373. actions[1].style = "display: none;";
  374. }
  375.  
  376. window._.addContent(null);
  377. window._.addTitle(`编辑标记 - ${name ? name : "#" + uid}`);
  378. window._.addContent(content);
  379. window._.show();
  380. };
  381. })();
  382.  
  383. // 猎巫
  384. const witchHunter = (() => {
  385. const key = "WITCH_HUNTER";
  386.  
  387. const data = GM_getValue(key) || {};
  388.  
  389. const add = async (fid, label) => {
  390. if (Object.values(data).find((item) => item.fid === fid)) {
  391. alert("已有相同版面ID");
  392. return;
  393. }
  394.  
  395. const info = await new Promise((resolve) => {
  396. fetch(`/thread.php?lite=js&fid=${fid}`)
  397. .then((res) => res.blob())
  398. .then((blob) => {
  399. const reader = new FileReader();
  400.  
  401. reader.onload = () => {
  402. const text = reader.result;
  403. const result = JSON.parse(
  404. text.replace("window.script_muti_get_var_store=", "")
  405. );
  406.  
  407. resolve(result.data);
  408. };
  409.  
  410. reader.readAsText(blob, "GBK");
  411. })
  412. .catch(() => {
  413. resolve({});
  414. });
  415. });
  416.  
  417. if (info.__F === undefined) {
  418. alert("版面ID有误");
  419. return;
  420. }
  421.  
  422. const name = info.__F.name;
  423.  
  424. const id = Math.max(...Object.values(data).map((item) => item.id), 0) + 1;
  425.  
  426. const hash = (() => {
  427. let h = 5381;
  428. for (var i = 0; i < label.length; i++) {
  429. h = ((h << 5) + h + label.charCodeAt(i)) & 0xffffffff;
  430. }
  431. return h;
  432. })();
  433.  
  434. const hex = Math.abs(hash).toString(16) + "000000";
  435.  
  436. const hsv = [
  437. `0x${hex.substring(2, 4)}` / 255,
  438. `0x${hex.substring(2, 4)}` / 255 / 2 + 0.25,
  439. `0x${hex.substring(4, 6)}` / 255 / 2 + 0.25,
  440. ];
  441.  
  442. const rgb = n.hsvToRgb(hsv[0], hsv[1], hsv[2]);
  443.  
  444. const color = ["#", ...rgb].reduce((a, b) => {
  445. return a + ("0" + b.toString(16)).slice(-2);
  446. });
  447.  
  448. data[id] = {
  449. id,
  450. fid,
  451. name,
  452. label,
  453. color,
  454. };
  455.  
  456. GM_setValue(key, data);
  457. };
  458.  
  459. const remove = (id) => {
  460. delete data[id];
  461.  
  462. GM_setValue(key, data);
  463. };
  464.  
  465. const run = (uid, element) => {
  466. Promise.all(
  467. Object.values(data).map(async (item) => {
  468. const api = `/thread.php?lite=js&fid=${item.fid}&authorid=${uid}`;
  469.  
  470. const verify =
  471. (await new Promise((resolve) => {
  472. fetch(api)
  473. .then((res) => res.blob())
  474. .then((blob) => {
  475. const reader = new FileReader();
  476.  
  477. reader.onload = () => {
  478. const text = reader.result;
  479. const result = JSON.parse(
  480. text.replace("window.script_muti_get_var_store=", "")
  481. );
  482.  
  483. if (result.error) {
  484. resolve(false);
  485. return;
  486. }
  487.  
  488. resolve(true);
  489. };
  490.  
  491. reader.readAsText(blob, "GBK");
  492. })
  493. .catch(() => {
  494. resolve(false);
  495. });
  496. })) ||
  497. (await new Promise((resolve) => {
  498. fetch(`${api}&searchpost=1`)
  499. .then((res) => res.blob())
  500. .then((blob) => {
  501. const reader = new FileReader();
  502.  
  503. reader.onload = () => {
  504. const text = reader.result;
  505. const result = JSON.parse(
  506. text.replace("window.script_muti_get_var_store=", "")
  507. );
  508.  
  509. if (result.error) {
  510. resolve(false);
  511. return;
  512. }
  513.  
  514. resolve(true);
  515. };
  516.  
  517. reader.readAsText(blob, "GBK");
  518. })
  519. .catch(() => {
  520. resolve(false);
  521. });
  522. }));
  523.  
  524. if (verify) {
  525. return item;
  526. }
  527. })
  528. ).then((res) => {
  529. res.forEach((item) => {
  530. if (item) {
  531. element.style.display = "block";
  532. element.innerHTML += `<b class="block_txt nobr" style="background:${item.color}; color:#fff; margin: 0.1em 0.2em;">${item.label}</b>`;
  533. }
  534. });
  535. });
  536. };
  537.  
  538. return {
  539. add,
  540. remove,
  541. run,
  542. data,
  543. };
  544. })();
  545.  
  546. // 小号过滤和声望过滤
  547. const getFilterModeByUserInfo = async (userInfo, reputation) => {
  548. const filterRegdateLimit = data.options.filterRegdateLimit || 0;
  549.  
  550. const filterPostnumLimit = data.options.filterPostnumLimit || 0;
  551.  
  552. const filterReputationLimit = data.options.filterReputationLimit || NaN;
  553.  
  554. if (userInfo) {
  555. const { regdate, postnum } = userInfo;
  556.  
  557. if (
  558. filterRegdateLimit > 0 &&
  559. regdate * 1000 > new Date() - filterRegdateLimit
  560. ) {
  561. return true;
  562. }
  563.  
  564. if (filterPostnumLimit > 0 && postnum < filterPostnumLimit) {
  565. return true;
  566. }
  567. }
  568.  
  569. if (Number.isNaN(filterReputationLimit) === false) {
  570. if (reputation < filterReputationLimit) {
  571. return true;
  572. }
  573. }
  574.  
  575. return false;
  576. };
  577.  
  578. // 判断过滤方式
  579. const getFilterMode = async (uid, subject, content) => {
  580. let result = -1;
  581.  
  582. const user = data.users[uid];
  583.  
  584. const tags = user ? user.tags.map((tag) => data.tags[tag]) : [];
  585.  
  586. const keywords = Object.values(data.keywords);
  587.  
  588. const locations = Object.values(data.locations);
  589.  
  590. if (uid && uid > 0) {
  591. const userInfo = n.userInfo.users[uid];
  592.  
  593. const reputation = (() => {
  594. const reputations = n.userInfo.reputations;
  595.  
  596. if (reputations) {
  597. for (let fid in reputations) {
  598. return reputations[fid][uid] || 0;
  599. }
  600. }
  601.  
  602. return NaN;
  603. })();
  604.  
  605. if (await getFilterModeByUserInfo(userInfo, reputation)) {
  606. return FILTER_MODE.indexOf("隐藏");
  607. }
  608. }
  609.  
  610. if (user) {
  611. const filterMode = FILTER_MODE.indexOf(user.filterMode);
  612.  
  613. if (filterMode > 0) {
  614. return filterMode;
  615. }
  616.  
  617. result = filterMode;
  618. }
  619.  
  620. if (tags.length) {
  621. const filterMode = (() => {
  622. if (tags.some((tag) => tag.filterMode !== "显示")) {
  623. return tags
  624. .filter((tag) => tag.filterMode !== "显示")
  625. .map((tag) => FILTER_MODE.indexOf(tag.filterMode) || 0)
  626. .sort((a, b) => b - a)[0];
  627. }
  628.  
  629. return FILTER_MODE.indexOf("显示");
  630. })();
  631.  
  632. if (filterMode > 0) {
  633. return filterMode;
  634. }
  635.  
  636. result = filterMode;
  637. }
  638.  
  639. if (keywords.length) {
  640. const filterMode = (() => {
  641. const sR = (() => {
  642. if (subject) {
  643. const r = keywords
  644. .filter((item) => item.keyword && item.filterMode !== "显示")
  645. .filter((item) => (item.filterLevel || 0) >= 0)
  646. .sort(
  647. (a, b) =>
  648. FILTER_MODE.indexOf(b.filterMode) -
  649. FILTER_MODE.indexOf(a.filterMode)
  650. )
  651. .find((item) => subject.search(item.keyword) >= 0);
  652.  
  653. if (r) {
  654. return FILTER_MODE.indexOf(r.filterMode);
  655. }
  656. }
  657.  
  658. return -1;
  659. })();
  660.  
  661. const cR = (() => {
  662. if (content) {
  663. const r = keywords
  664. .filter((item) => item.keyword && item.filterMode !== "显示")
  665. .filter((item) => (item.filterLevel || 0) >= 1)
  666. .sort(
  667. (a, b) =>
  668. FILTER_MODE.indexOf(b.filterMode) -
  669. FILTER_MODE.indexOf(a.filterMode)
  670. )
  671. .find((item) => content.search(item.keyword) >= 0);
  672.  
  673. if (r) {
  674. return FILTER_MODE.indexOf(r.filterMode);
  675. }
  676. }
  677.  
  678. return -1;
  679. })();
  680.  
  681. return Math.max(sR, cR, result);
  682. })();
  683.  
  684. if (filterMode > 0) {
  685. return filterMode;
  686. }
  687.  
  688. result = filterMode;
  689. }
  690.  
  691. if (locations.length) {
  692. const { ipLoc } = await new Promise((resolve) => {
  693. fetch(`/nuke.php?lite=js&__lib=ucp&__act=get&uid=${uid}`)
  694. .then((res) => res.blob())
  695. .then((blob) => {
  696. const reader = new FileReader();
  697.  
  698. reader.onload = () => {
  699. const text = reader.result;
  700. const result = JSON.parse(
  701. text.replace("window.script_muti_get_var_store=", "")
  702. );
  703.  
  704. resolve(result.data[0]);
  705. };
  706.  
  707. reader.readAsText(blob, "GBK");
  708. })
  709. .catch(() => {
  710. resolve({});
  711. });
  712. });
  713.  
  714. if (ipLoc) {
  715. const filterMode = (() => {
  716. const r = locations
  717. .filter((item) => item.keyword && item.filterMode !== "显示")
  718. .sort(
  719. (a, b) =>
  720. FILTER_MODE.indexOf(b.filterMode) -
  721. FILTER_MODE.indexOf(a.filterMode)
  722. )
  723. .find((item) => ipLoc.search(item.keyword) >= 0);
  724.  
  725. if (r) {
  726. return FILTER_MODE.indexOf(r.filterMode);
  727. }
  728.  
  729. return Math.max(r, result);
  730. })();
  731.  
  732. if (filterMode > 0) {
  733. return filterMode;
  734. }
  735.  
  736. result = filterMode;
  737. }
  738. }
  739.  
  740. return result;
  741. };
  742.  
  743. // 根据 TID 获取过滤方式
  744. const getFilterModeByTopic = async (tid) => {
  745. return await new Promise((resolve, reject) => {
  746. const api = `/read.php?tid=${tid}`;
  747.  
  748. fetch(api)
  749. .then((res) => res.blob())
  750. .then((blob) => {
  751. const getLastIndex = (content, position) => {
  752. if (position >= 0) {
  753. let nextIndex = position + 1;
  754.  
  755. while (nextIndex < content.length) {
  756. if (content[nextIndex] === "}") {
  757. return nextIndex;
  758. }
  759.  
  760. if (content[nextIndex] === "{") {
  761. nextIndex = getLastIndex(content, nextIndex);
  762.  
  763. if (nextIndex < 0) {
  764. break;
  765. }
  766. }
  767.  
  768. nextIndex = nextIndex + 1;
  769. }
  770. }
  771.  
  772. return -1;
  773. };
  774.  
  775. const reader = new FileReader();
  776.  
  777. reader.onload = async () => {
  778. const parser = new DOMParser();
  779.  
  780. const doc = parser.parseFromString(reader.result, "text/html");
  781.  
  782. const html = doc.body.innerHTML;
  783.  
  784. // 验证帖子正常
  785. const verify = doc.querySelector("#m_posts");
  786.  
  787. if (verify) {
  788. // 取得顶楼 UID
  789. const uid = (() => {
  790. const ele = doc.querySelector("#postauthor0");
  791.  
  792. if (ele) {
  793. const res = ele.getAttribute("href").match(/uid=(\S+)/);
  794.  
  795. if (res) {
  796. return res[1];
  797. }
  798. }
  799.  
  800. return 0;
  801. })();
  802.  
  803. // 取得顶楼标题
  804. const subject = doc.querySelector("#postsubject0").innerHTML;
  805.  
  806. // 取得顶楼内容
  807. const content = doc.querySelector("#postcontent0").innerHTML;
  808.  
  809. if (uid && uid > 0) {
  810. // 取得用户信息
  811. const userInfo = (() => {
  812. // 起始JSON
  813. const str = `"${uid}":{`;
  814.  
  815. // 起始下标
  816. const index = html.indexOf(str) + str.length;
  817.  
  818. // 结尾下标
  819. const lastIndex = getLastIndex(html, index);
  820.  
  821. if (lastIndex >= 0) {
  822. return JSON.parse(`{${html.substring(index, lastIndex)}}`);
  823. }
  824.  
  825. return null;
  826. })();
  827.  
  828. // 取得用户声望
  829. const reputation = (() => {
  830. const reputations = (() => {
  831. // 起始JSON
  832. const str = `"__REPUTATIONS":{`;
  833.  
  834. // 起始下标
  835. const index = html.indexOf(str) + str.length;
  836.  
  837. // 结尾下标
  838. const lastIndex = getLastIndex(html, index);
  839.  
  840. if (lastIndex >= 0) {
  841. return JSON.parse(
  842. `{${html.substring(index, lastIndex)}}`
  843. );
  844. }
  845.  
  846. return null;
  847. })();
  848.  
  849. if (reputations) {
  850. for (let fid in reputations) {
  851. return reputations[fid][uid] || 0;
  852. }
  853. }
  854.  
  855. return NaN;
  856. })();
  857.  
  858. if (await getFilterModeByUserInfo(userInfo, reputation)) {
  859. resolve(FILTER_MODE.indexOf("隐藏"));
  860. }
  861. }
  862.  
  863. resolve(getFilterMode(uid, subject, content));
  864. } else {
  865. reject();
  866. }
  867. };
  868.  
  869. reader.readAsText(blob, "GBK");
  870. })
  871. .catch(() => {
  872. reject();
  873. });
  874. }).catch(() => {
  875. return FILTER_MODE.indexOf("隐藏");
  876. });
  877. };
  878.  
  879. // 处理引用
  880. const handleQuote = async (content) => {
  881. const quotes = content.querySelectorAll(".quote");
  882.  
  883. await Promise.all(
  884. [...quotes].map(async (quote) => {
  885. const uid = (() => {
  886. const ele = quote.querySelector("a[href^='/nuke.php']");
  887.  
  888. if (ele) {
  889. const res = ele.getAttribute("href").match(/uid=(\S+)/);
  890.  
  891. if (res) {
  892. return res[1];
  893. }
  894. }
  895.  
  896. return 0;
  897. })();
  898.  
  899. const filterMode = await new Promise(async (resolve) => {
  900. const mode = await getFilterMode(uid, "", quote.innerText);
  901.  
  902. if (mode === 0) {
  903. resolve(data.options.filterMode);
  904. }
  905.  
  906. if (mode > 0) {
  907. resolve(FILTER_MODE[mode]);
  908. }
  909.  
  910. resolve("");
  911. });
  912.  
  913. if (filterMode === "标记") {
  914. quote.innerHTML = `
  915. <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
  916. <span class="crimson">Troll must die.</span>
  917. <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
  918. <div style="display: none;" name="troll_${uid}">
  919. ${quote.innerHTML}
  920. </div>
  921. </div>`;
  922. } else if (filterMode === "遮罩") {
  923. const source = document.createElement("DIV");
  924.  
  925. source.innerHTML = quote.innerHTML;
  926. source.style.display = "none";
  927.  
  928. const caption = document.createElement("CAPTION");
  929.  
  930. caption.className = "filter-mask filter-mask-block";
  931.  
  932. caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
  933. caption.onclick = () => {
  934. quote.removeChild(caption);
  935.  
  936. source.style.display = "";
  937. };
  938.  
  939. quote.innerHTML = "";
  940. quote.appendChild(source);
  941. quote.appendChild(caption);
  942. } else if (filterMode === "隐藏") {
  943. quote.innerHTML = "";
  944. }
  945. })
  946. );
  947. };
  948.  
  949. // 过滤
  950. const reFilter = (() => {
  951. let hasNext = false;
  952. let isRunning = false;
  953.  
  954. const func = async () => {
  955. const tPage = location.pathname === "/thread.php";
  956. const pPage = location.pathname === "/read.php";
  957.  
  958. if (tPage) {
  959. const params = new URLSearchParams(location.search);
  960.  
  961. if (params.has("favor")) {
  962. return;
  963. }
  964.  
  965. if (params.has("authorid")) {
  966. return;
  967. }
  968. }
  969.  
  970. if (tPage) {
  971. const tData = n.topicArg.data;
  972.  
  973. await Promise.all(
  974. Object.values(tData).map(async (item) => {
  975. if (item.containerC) return;
  976.  
  977. const tid = item[8];
  978.  
  979. const filterMode = await new Promise(async (resolve) => {
  980. const mode = await getFilterModeByTopic(tid);
  981.  
  982. if (mode === 0) {
  983. resolve(data.options.filterMode);
  984. }
  985.  
  986. if (mode > 0) {
  987. resolve(FILTER_MODE[mode]);
  988. }
  989.  
  990. resolve("");
  991. });
  992.  
  993. item.contentC = item[1];
  994.  
  995. item.contentB = item.contentB || item.contentC.innerHTML;
  996.  
  997. item.containerC =
  998. item.containerC || item.contentC.parentNode.parentNode;
  999.  
  1000. item.containerC.style = "";
  1001. item.contentC.style = "";
  1002. item[1].className = item[1].className.replace(" filter-mask", "");
  1003. item[2].className = item[2].className.replace(" filter-mask", "");
  1004.  
  1005. if (filterMode === "标记") {
  1006. item.contentC.style = "text-decoration: line-through;";
  1007. } else if (filterMode === "遮罩") {
  1008. item[1].className += " filter-mask";
  1009. item[2].className += " filter-mask";
  1010. } else if (filterMode === "隐藏") {
  1011. item.containerC.style = "display: none;";
  1012. }
  1013. })
  1014. );
  1015. } else if (pPage) {
  1016. const pData = n.postArg.data;
  1017.  
  1018. await Promise.all(
  1019. Object.values(pData).map(async (item) => {
  1020. if (~~item.pAid === self) return;
  1021. if (item.containerC) return;
  1022.  
  1023. if (typeof item.i === "number") {
  1024. item.actionC =
  1025. item.actionC ||
  1026. (() => {
  1027. const ele = item.uInfoC.querySelector('[name="uid"]');
  1028.  
  1029. ele.onclick = null;
  1030.  
  1031. return ele;
  1032. })();
  1033.  
  1034. item.tagC =
  1035. item.tagC ||
  1036. (() => {
  1037. const tc = document.createElement("div");
  1038.  
  1039. tc.className = "filter-tags";
  1040.  
  1041. item.uInfoC.appendChild(tc);
  1042.  
  1043. return tc;
  1044. })();
  1045. }
  1046.  
  1047. item.pName =
  1048. item.pName ||
  1049. item.uInfoC.getElementsByClassName("author")[0].innerText;
  1050.  
  1051. item.reFilter =
  1052. item.reFilter ||
  1053. (async () => {
  1054. const uid = item.pAid;
  1055.  
  1056. const filterMode = await new Promise(async (resolve) => {
  1057. const mode = await getFilterMode(
  1058. uid,
  1059. item.subjectC.innerText,
  1060. item.contentC.innerText
  1061. );
  1062.  
  1063. if (mode === 0) {
  1064. resolve(data.options.filterMode);
  1065. }
  1066.  
  1067. if (mode > 0) {
  1068. resolve(FILTER_MODE[mode]);
  1069. }
  1070.  
  1071. resolve("");
  1072. });
  1073.  
  1074. item.avatarC =
  1075. item.avatarC ||
  1076. (() => {
  1077. const tc = document.createElement("div");
  1078.  
  1079. const avatar = document.getElementById(
  1080. `posteravatar${item.i}`
  1081. );
  1082.  
  1083. if (avatar) {
  1084. avatar.parentNode.insertBefore(tc, avatar.nextSibling);
  1085.  
  1086. tc.appendChild(avatar);
  1087. }
  1088.  
  1089. return tc;
  1090. })();
  1091.  
  1092. item.contentB = item.contentB || item.contentC.innerHTML;
  1093.  
  1094. item.containerC =
  1095. item.containerC ||
  1096. (() => {
  1097. let temp = item.contentC;
  1098.  
  1099. if (item.i >= 0) {
  1100. while (temp.nodeName !== "TBODY") {
  1101. temp = temp.parentNode;
  1102. }
  1103. } else {
  1104. while (temp.nodeName !== "DIV") {
  1105. temp = temp.parentNode;
  1106. }
  1107. }
  1108.  
  1109. return temp;
  1110. })();
  1111.  
  1112. item.avatarC.style.display = "";
  1113. item.containerC.style.display = "";
  1114. item.contentC.innerHTML = item.contentB;
  1115.  
  1116. if (item.actionC) {
  1117. item.actionC.style = "background: #aaa;";
  1118. }
  1119.  
  1120. if (filterMode === "标记") {
  1121. item.avatarC.style.display = "none";
  1122. item.contentC.innerHTML = `
  1123. <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
  1124. <span class="crimson">Troll must die.</span>
  1125. <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
  1126. <div style="display: none;" name="troll_${uid}">
  1127. ${item.contentB}
  1128. </div>
  1129. </div>`;
  1130.  
  1131. if (item.actionC && data.users[uid]) {
  1132. item.actionC.style = "background: #cb4042;";
  1133. }
  1134. } else if (filterMode === "遮罩") {
  1135. const caption = document.createElement("CAPTION");
  1136.  
  1137. if (item.i >= 0) {
  1138. caption.className = "filter-mask filter-mask-block";
  1139. } else {
  1140. caption.className = "filter-mask filter-mask-block left";
  1141. caption.style = "width: 47%;";
  1142. }
  1143.  
  1144. caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
  1145. caption.onclick = () => {
  1146. item.containerC.parentNode.removeChild(caption);
  1147. item.containerC.style.display = "";
  1148. };
  1149.  
  1150. item.containerC.parentNode.insertBefore(
  1151. caption,
  1152. item.containerC
  1153. );
  1154. item.containerC.style.display = "none";
  1155.  
  1156. if (item.actionC && data.users[uid]) {
  1157. item.actionC.style = "background: #cb4042;";
  1158. }
  1159. } else if (filterMode === "隐藏") {
  1160. item.containerC.style.display = "none";
  1161. } else {
  1162. await handleQuote(item.contentC);
  1163. }
  1164.  
  1165. if (item.tagC) {
  1166. const tags = data.users[uid]
  1167. ? data.users[uid].tags.map((tag) => data.tags[tag]) || []
  1168. : [];
  1169.  
  1170. item.tagC.style.display = tags.length ? "" : "none";
  1171. item.tagC.innerHTML = tags
  1172. .map(
  1173. (tag) =>
  1174. `<b class="block_txt nobr" style="background:${tag.color}; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>`
  1175. )
  1176. .join("");
  1177.  
  1178. witchHunter.run(uid, item.tagC);
  1179. }
  1180. });
  1181.  
  1182. if (item.actionC) {
  1183. item.actionC.onclick =
  1184. item.actionC.onclick ||
  1185. ((e) => {
  1186. if (item.pAid < 0) return;
  1187.  
  1188. const user = data.users[item.pAid];
  1189.  
  1190. if (e.ctrlKey === false) {
  1191. editUser(item.pAid, item.pName, item.reFilter);
  1192. } else {
  1193. if (user) {
  1194. delete data.users[user.id];
  1195. } else {
  1196. addUser(item.pAid, item.pName);
  1197. }
  1198.  
  1199. saveData();
  1200. item.reFilter();
  1201. }
  1202. });
  1203. }
  1204.  
  1205. await item.reFilter();
  1206. })
  1207. );
  1208. }
  1209. };
  1210.  
  1211. const execute = () =>
  1212. func().finally(() => {
  1213. if (hasNext) {
  1214. hasNext = false;
  1215.  
  1216. execute();
  1217. } else {
  1218. isRunning = false;
  1219. }
  1220. });
  1221.  
  1222. return async () => {
  1223. if (isRunning) {
  1224. hasNext = true;
  1225. } else {
  1226. isRunning = true;
  1227.  
  1228. await execute();
  1229. }
  1230. };
  1231. })();
  1232.  
  1233. // STYLE
  1234. GM_addStyle(`
  1235. .filter-table-wrapper {
  1236. max-height: 80vh;
  1237. overflow-y: auto;
  1238. }
  1239. .filter-table {
  1240. margin: 0;
  1241. }
  1242. .filter-table th,
  1243. .filter-table td {
  1244. position: relative;
  1245. white-space: nowrap;
  1246. }
  1247. .filter-table th {
  1248. position: sticky;
  1249. top: 2px;
  1250. z-index: 1;
  1251. }
  1252. .filter-table input:not([type]), .filter-table input[type="text"] {
  1253. margin: 0;
  1254. box-sizing: border-box;
  1255. height: 100%;
  1256. width: 100%;
  1257. }
  1258. .filter-input-wrapper {
  1259. position: absolute;
  1260. top: 6px;
  1261. right: 6px;
  1262. bottom: 6px;
  1263. left: 6px;
  1264. }
  1265. .filter-text-ellipsis {
  1266. display: flex;
  1267. }
  1268. .filter-text-ellipsis > * {
  1269. flex: 1;
  1270. width: 1px;
  1271. overflow: hidden;
  1272. text-overflow: ellipsis;
  1273. }
  1274. .filter-button-group {
  1275. margin: -.1em -.2em;
  1276. }
  1277. .filter-tags {
  1278. margin: 2px -0.2em 0;
  1279. text-align: left;
  1280. }
  1281. .filter-mask {
  1282. margin: 1px;
  1283. color: #81C7D4;
  1284. background: #81C7D4;
  1285. }
  1286. .filter-mask-block {
  1287. display: block;
  1288. border: 1px solid #66BAB7;
  1289. text-align: center !important;
  1290. }
  1291. .filter-input-wrapper {
  1292. position: absolute;
  1293. top: 6px;
  1294. right: 6px;
  1295. bottom: 6px;
  1296. left: 6px;
  1297. }
  1298. `);
  1299.  
  1300. // UI
  1301. const u = (() => {
  1302. const modules = {};
  1303.  
  1304. const tabContainer = (() => {
  1305. const c = document.createElement("div");
  1306.  
  1307. c.className = "w100";
  1308. c.innerHTML = `
  1309. <div class="right_" style="margin-bottom: 5px;">
  1310. <table class="stdbtn" cellspacing="0">
  1311. <tbody>
  1312. <tr></tr>
  1313. </tbody>
  1314. </table>
  1315. </div>
  1316. <div class="clear"></div>
  1317. `;
  1318.  
  1319. return c;
  1320. })();
  1321.  
  1322. const tabPanelContainer = (() => {
  1323. const c = document.createElement("div");
  1324.  
  1325. c.style = "width: 80vw;";
  1326.  
  1327. return c;
  1328. })();
  1329.  
  1330. const content = (() => {
  1331. const c = document.createElement("div");
  1332.  
  1333. c.append(tabContainer);
  1334. c.append(tabPanelContainer);
  1335.  
  1336. return c;
  1337. })();
  1338.  
  1339. const addModule = (() => {
  1340. const tc = tabContainer.getElementsByTagName("tr")[0];
  1341. const cc = tabPanelContainer;
  1342.  
  1343. return (module) => {
  1344. const tabBox = document.createElement("td");
  1345.  
  1346. tabBox.innerHTML = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`;
  1347.  
  1348. const tab = tabBox.childNodes[0];
  1349.  
  1350. const toggle = () => {
  1351. Object.values(modules).forEach((item) => {
  1352. if (item.tab === tab) {
  1353. item.tab.className = "nobr";
  1354. item.content.style = "display: block";
  1355. item.refresh();
  1356. } else {
  1357. item.tab.className = "nobr silver";
  1358. item.content.style = "display: none";
  1359. }
  1360. });
  1361. };
  1362.  
  1363. tc.append(tabBox);
  1364. cc.append(module.content);
  1365.  
  1366. tab.onclick = toggle;
  1367.  
  1368. modules[module.name] = {
  1369. ...module,
  1370. tab,
  1371. toggle,
  1372. };
  1373.  
  1374. return modules[module.name];
  1375. };
  1376. })();
  1377.  
  1378. return {
  1379. content,
  1380. modules,
  1381. addModule,
  1382. };
  1383. })();
  1384.  
  1385. // 用户
  1386. const userModule = (() => {
  1387. const content = (() => {
  1388. const c = document.createElement("div");
  1389.  
  1390. c.style = "display: none";
  1391. c.innerHTML = `
  1392. <div class="filter-table-wrapper">
  1393. <table class="filter-table forumbox">
  1394. <thead>
  1395. <tr class="block_txt_c0">
  1396. <th class="c1" width="1">昵称</th>
  1397. <th class="c2">标记</th>
  1398. <th class="c3" width="1">过滤方式</th>
  1399. <th class="c4" width="1">操作</th>
  1400. </tr>
  1401. </thead>
  1402. <tbody></tbody>
  1403. </table>
  1404. </div>
  1405. `;
  1406.  
  1407. return c;
  1408. })();
  1409.  
  1410. const refresh = (() => {
  1411. const container = content.getElementsByTagName("tbody")[0];
  1412.  
  1413. const func = () => {
  1414. container.innerHTML = "";
  1415.  
  1416. Object.values(data.users).forEach((item) => {
  1417. const tc = document.createElement("tr");
  1418.  
  1419. tc.className = `row${
  1420. (container.querySelectorAll("TR").length % 2) + 1
  1421. }`;
  1422.  
  1423. tc.refresh = () => {
  1424. if (data.users[item.id]) {
  1425. tc.innerHTML = `
  1426. <td class="c1">
  1427. <a href="/nuke.php?func=ucp&uid=${
  1428. item.id
  1429. }" class="b nobr">[${
  1430. item.name ? "@" + item.name : "#" + item.id
  1431. }]</a>
  1432. </td>
  1433. <td class="c2">
  1434. ${item.tags
  1435. .map((tag) => {
  1436. if (data.tags[tag]) {
  1437. return `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`;
  1438. }
  1439. })
  1440. .join("")}
  1441. </td>
  1442. <td class="c3">
  1443. <div class="filter-table-button-group">
  1444. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1445. </div>
  1446. </td>
  1447. <td class="c4">
  1448. <div class="filter-table-button-group">
  1449. <button>编辑</button>
  1450. <button>删除</button>
  1451. </div>
  1452. </td>
  1453. `;
  1454.  
  1455. const actions = tc.getElementsByTagName("button");
  1456.  
  1457. actions[0].onclick = () => {
  1458. data.users[item.id].filterMode = switchFilterMode(
  1459. data.users[item.id].filterMode || FILTER_MODE[0]
  1460. );
  1461.  
  1462. actions[0].innerHTML = data.users[item.id].filterMode;
  1463.  
  1464. saveData();
  1465. reFilter();
  1466. };
  1467.  
  1468. actions[1].onclick = () => {
  1469. editUser(item.id, item.name, tc.refresh);
  1470. };
  1471.  
  1472. actions[2].onclick = () => {
  1473. if (confirm("是否确认?")) {
  1474. delete data.users[item.id];
  1475. container.removeChild(tc);
  1476.  
  1477. saveData();
  1478. reFilter();
  1479. }
  1480. };
  1481. } else {
  1482. tc.remove();
  1483. }
  1484. };
  1485.  
  1486. tc.refresh();
  1487.  
  1488. container.appendChild(tc);
  1489. });
  1490. };
  1491.  
  1492. return func;
  1493. })();
  1494.  
  1495. return {
  1496. name: "用户",
  1497. content,
  1498. refresh,
  1499. };
  1500. })();
  1501.  
  1502. // 标记
  1503. const tagModule = (() => {
  1504. const content = (() => {
  1505. const c = document.createElement("div");
  1506.  
  1507. c.style = "display: none";
  1508. c.innerHTML = `
  1509. <div class="filter-table-wrapper">
  1510. <table class="filter-table forumbox">
  1511. <thead>
  1512. <tr class="block_txt_c0">
  1513. <th class="c1" width="1">标记</th>
  1514. <th class="c2">列表</th>
  1515. <th class="c3" width="1">过滤方式</th>
  1516. <th class="c4" width="1">操作</th>
  1517. </tr>
  1518. </thead>
  1519. <tbody></tbody>
  1520. </table>
  1521. </div>
  1522. `;
  1523.  
  1524. return c;
  1525. })();
  1526.  
  1527. const refresh = (() => {
  1528. const container = content.getElementsByTagName("tbody")[0];
  1529.  
  1530. const func = () => {
  1531. container.innerHTML = "";
  1532.  
  1533. Object.values(data.tags).forEach((item) => {
  1534. const tc = document.createElement("tr");
  1535.  
  1536. tc.className = `row${
  1537. (container.querySelectorAll("TR").length % 2) + 1
  1538. }`;
  1539.  
  1540. tc.innerHTML = `
  1541. <td class="c1">
  1542. <b class="block_txt nobr" style="background:${
  1543. item.color
  1544. }; color:#fff; margin: 0.1em 0.2em;">${item.name}</b>
  1545. </td>
  1546. <td class="c2">
  1547. <button>${
  1548. Object.values(data.users).filter((user) =>
  1549. user.tags.find((tag) => tag === item.id)
  1550. ).length
  1551. }
  1552. </button>
  1553. <div style="white-space: normal; display: none;">
  1554. ${Object.values(data.users)
  1555. .filter((user) =>
  1556. user.tags.find((tag) => tag === item.id)
  1557. )
  1558. .map(
  1559. (user) =>
  1560. `<a href="/nuke.php?func=ucp&uid=${
  1561. user.id
  1562. }" class="b nobr">[${
  1563. user.name ? "@" + user.name : "#" + user.id
  1564. }]</a>`
  1565. )
  1566. .join("")}
  1567. </div>
  1568. </td>
  1569. <td class="c3">
  1570. <div class="filter-table-button-group">
  1571. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1572. </div>
  1573. </td>
  1574. <td class="c4">
  1575. <div class="filter-table-button-group">
  1576. <button>删除</button>
  1577. </div>
  1578. </td>
  1579. `;
  1580.  
  1581. const actions = tc.getElementsByTagName("button");
  1582.  
  1583. actions[0].onclick = (() => {
  1584. let hide = true;
  1585. return () => {
  1586. hide = !hide;
  1587. actions[0].nextElementSibling.style.display = hide
  1588. ? "none"
  1589. : "block";
  1590. };
  1591. })();
  1592.  
  1593. actions[1].onclick = () => {
  1594. data.tags[item.id].filterMode = switchFilterMode(
  1595. data.tags[item.id].filterMode || FILTER_MODE[0]
  1596. );
  1597.  
  1598. actions[1].innerHTML = data.tags[item.id].filterMode;
  1599.  
  1600. saveData();
  1601. reFilter();
  1602. };
  1603.  
  1604. actions[2].onclick = () => {
  1605. if (confirm("是否确认?")) {
  1606. delete data.tags[item.id];
  1607.  
  1608. Object.values(data.users).forEach((user) => {
  1609. const index = user.tags.findIndex((tag) => tag === item.id);
  1610. if (index >= 0) {
  1611. user.tags.splice(index, 1);
  1612. }
  1613. });
  1614.  
  1615. container.removeChild(tc);
  1616.  
  1617. saveData();
  1618. reFilter();
  1619. }
  1620. };
  1621.  
  1622. container.appendChild(tc);
  1623. });
  1624. };
  1625.  
  1626. return func;
  1627. })();
  1628.  
  1629. return {
  1630. name: "标记",
  1631. content,
  1632. refresh,
  1633. };
  1634. })();
  1635.  
  1636. // 关键字
  1637. const keywordModule = (() => {
  1638. const content = (() => {
  1639. const c = document.createElement("div");
  1640.  
  1641. c.style = "display: none";
  1642. c.innerHTML = `
  1643. <div class="filter-table-wrapper">
  1644. <table class="filter-table forumbox">
  1645. <thead>
  1646. <tr class="block_txt_c0">
  1647. <th class="c1">列表</th>
  1648. <th class="c2" width="1">过滤方式</th>
  1649. <th class="c3" width="1">包括内容</th>
  1650. <th class="c4" width="1">操作</th>
  1651. </tr>
  1652. </thead>
  1653. <tbody></tbody>
  1654. </table>
  1655. </div>
  1656. <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用&quot;|&quot;隔开,&quot;ABC|DEF&quot;即为屏蔽带有ABC或者DEF的内容。</div>
  1657. `;
  1658.  
  1659. return c;
  1660. })();
  1661.  
  1662. const refresh = (() => {
  1663. const container = content.getElementsByTagName("tbody")[0];
  1664.  
  1665. const func = () => {
  1666. container.innerHTML = "";
  1667.  
  1668. Object.values(data.keywords).forEach((item) => {
  1669. const tc = document.createElement("tr");
  1670.  
  1671. tc.className = `row${
  1672. (container.querySelectorAll("TR").length % 2) + 1
  1673. }`;
  1674.  
  1675. tc.innerHTML = `
  1676. <td class="c1">
  1677. <div class="filter-input-wrapper">
  1678. <input value="${item.keyword || ""}" />
  1679. </div>
  1680. </td>
  1681. <td class="c2">
  1682. <div class="filter-table-button-group">
  1683. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1684. </div>
  1685. </td>
  1686. <td class="c3">
  1687. <div style="text-align: center;">
  1688. <input type="checkbox" ${
  1689. item.filterLevel ? `checked="checked"` : ""
  1690. } />
  1691. </div>
  1692. </td>
  1693. <td class="c4">
  1694. <div class="filter-table-button-group">
  1695. <button>保存</button>
  1696. <button>删除</button>
  1697. </div>
  1698. </td>
  1699. `;
  1700.  
  1701. const inputElement = tc.querySelector("INPUT");
  1702. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1703. const actions = tc.getElementsByTagName("button");
  1704.  
  1705. actions[0].onclick = () => {
  1706. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1707. };
  1708.  
  1709. actions[1].onclick = () => {
  1710. if (inputElement.value) {
  1711. data.keywords[item.id] = {
  1712. id: item.id,
  1713. keyword: inputElement.value,
  1714. filterMode: actions[0].innerHTML,
  1715. filterLevel: levelElement.checked ? 1 : 0,
  1716. };
  1717.  
  1718. saveData();
  1719. refresh();
  1720. }
  1721. };
  1722.  
  1723. actions[2].onclick = () => {
  1724. if (confirm("是否确认?")) {
  1725. delete data.keywords[item.id];
  1726.  
  1727. saveData();
  1728. refresh();
  1729. }
  1730. };
  1731.  
  1732. container.appendChild(tc);
  1733. });
  1734.  
  1735. {
  1736. const tc = document.createElement("tr");
  1737.  
  1738. tc.className = `row${
  1739. (container.querySelectorAll("TR").length % 2) + 1
  1740. }`;
  1741.  
  1742. tc.innerHTML = `
  1743. <td class="c1">
  1744. <div class="filter-input-wrapper">
  1745. <input value="" />
  1746. </div>
  1747. </td>
  1748. <td class="c2">
  1749. <div class="filter-table-button-group">
  1750. <button>${FILTER_MODE[0]}</button>
  1751. </div>
  1752. </td>
  1753. <td class="c3">
  1754. <div style="text-align: center;">
  1755. <input type="checkbox" />
  1756. </div>
  1757. </td>
  1758. <td class="c4">
  1759. <div class="filter-table-button-group">
  1760. <button>添加</button>
  1761. </div>
  1762. </td>
  1763. `;
  1764.  
  1765. const inputElement = tc.querySelector("INPUT");
  1766. const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
  1767. const actions = tc.getElementsByTagName("button");
  1768.  
  1769. actions[0].onclick = () => {
  1770. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1771. };
  1772.  
  1773. actions[1].onclick = () => {
  1774. if (inputElement.value) {
  1775. addKeyword(
  1776. inputElement.value,
  1777. actions[0].innerHTML,
  1778. levelElement.checked ? 1 : 0
  1779. );
  1780.  
  1781. saveData();
  1782. refresh();
  1783. }
  1784. };
  1785.  
  1786. container.appendChild(tc);
  1787. }
  1788. };
  1789.  
  1790. return func;
  1791. })();
  1792.  
  1793. return {
  1794. name: "关键字",
  1795. content,
  1796. refresh,
  1797. };
  1798. })();
  1799.  
  1800. // 属地
  1801. const locationModule = (() => {
  1802. const content = (() => {
  1803. const c = document.createElement("div");
  1804.  
  1805. c.style = "display: none";
  1806. c.innerHTML = `
  1807. <div class="filter-table-wrapper">
  1808. <table class="filter-table forumbox">
  1809. <thead>
  1810. <tr class="block_txt_c0">
  1811. <th class="c1">列表</th>
  1812. <th class="c2" width="1">过滤方式</th>
  1813. <th class="c3" width="1">操作</th>
  1814. </tr>
  1815. </thead>
  1816. <tbody></tbody>
  1817. </table>
  1818. </div>
  1819. <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用&quot;|&quot;隔开,&quot;ABC|DEF&quot;即为屏蔽带有ABC或者DEF的内容。<br/>属地过滤功能需要占用额外的资源,请谨慎开启</div>
  1820. `;
  1821.  
  1822. return c;
  1823. })();
  1824.  
  1825. const refresh = (() => {
  1826. const container = content.getElementsByTagName("tbody")[0];
  1827.  
  1828. const func = () => {
  1829. container.innerHTML = "";
  1830.  
  1831. Object.values(data.locations).forEach((item) => {
  1832. const tc = document.createElement("tr");
  1833.  
  1834. tc.className = `row${
  1835. (container.querySelectorAll("TR").length % 2) + 1
  1836. }`;
  1837.  
  1838. tc.innerHTML = `
  1839. <td class="c1">
  1840. <div class="filter-input-wrapper">
  1841. <input value="${item.keyword || ""}" />
  1842. </div>
  1843. </td>
  1844. <td class="c2">
  1845. <div class="filter-table-button-group">
  1846. <button>${item.filterMode || FILTER_MODE[0]}</button>
  1847. </div>
  1848. </td>
  1849. <td class="c3">
  1850. <div class="filter-table-button-group">
  1851. <button>保存</button>
  1852. <button>删除</button>
  1853. </div>
  1854. </td>
  1855. `;
  1856.  
  1857. const inputElement = tc.querySelector("INPUT");
  1858. const actions = tc.getElementsByTagName("button");
  1859.  
  1860. actions[0].onclick = () => {
  1861. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1862. };
  1863.  
  1864. actions[1].onclick = () => {
  1865. if (inputElement.value) {
  1866. data.locations[item.id] = {
  1867. id: item.id,
  1868. keyword: inputElement.value,
  1869. filterMode: actions[0].innerHTML,
  1870. };
  1871.  
  1872. saveData();
  1873. refresh();
  1874. }
  1875. };
  1876.  
  1877. actions[2].onclick = () => {
  1878. if (confirm("是否确认?")) {
  1879. delete data.locations[item.id];
  1880.  
  1881. saveData();
  1882. refresh();
  1883. }
  1884. };
  1885.  
  1886. container.appendChild(tc);
  1887. });
  1888.  
  1889. {
  1890. const tc = document.createElement("tr");
  1891.  
  1892. tc.className = `row${
  1893. (container.querySelectorAll("TR").length % 2) + 1
  1894. }`;
  1895.  
  1896. tc.innerHTML = `
  1897. <td class="c1">
  1898. <div class="filter-input-wrapper">
  1899. <input value="" />
  1900. </div>
  1901. </td>
  1902. <td class="c2">
  1903. <div class="filter-table-button-group">
  1904. <button>${FILTER_MODE[0]}</button>
  1905. </div>
  1906. </td>
  1907. <td class="c3">
  1908. <div class="filter-table-button-group">
  1909. <button>添加</button>
  1910. </div>
  1911. </td>
  1912. `;
  1913.  
  1914. const inputElement = tc.querySelector("INPUT");
  1915. const actions = tc.getElementsByTagName("button");
  1916.  
  1917. actions[0].onclick = () => {
  1918. actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
  1919. };
  1920.  
  1921. actions[1].onclick = () => {
  1922. if (inputElement.value) {
  1923. addLocation(inputElement.value, actions[0].innerHTML);
  1924.  
  1925. saveData();
  1926. refresh();
  1927. }
  1928. };
  1929.  
  1930. container.appendChild(tc);
  1931. }
  1932. };
  1933.  
  1934. return func;
  1935. })();
  1936.  
  1937. return {
  1938. name: "属地",
  1939. content,
  1940. refresh,
  1941. };
  1942. })();
  1943.  
  1944. // 猎巫
  1945. const witchHuntModule = (() => {
  1946. const content = (() => {
  1947. const c = document.createElement("div");
  1948.  
  1949. c.style = "display: none";
  1950. c.innerHTML = `
  1951. <div class="filter-table-wrapper">
  1952. <table class="filter-table forumbox">
  1953. <thead>
  1954. <tr class="block_txt_c0">
  1955. <th class="c1">版面</th>
  1956. <th class="c2">标签</th>
  1957. <th class="c3" width="1">操作</th>
  1958. </tr>
  1959. </thead>
  1960. <tbody></tbody>
  1961. </table>
  1962. </div>
  1963. <div class="silver" style="margin-top: 10px;">猎巫模块需要占用额外的资源,请谨慎开启<br/>该功能为实验性功能,仅判断用户是否曾经在某个版面发言<br/>未来可能会加入发言的筛选或是屏蔽功能,也可能移除此功能</div>
  1964. `;
  1965.  
  1966. return c;
  1967. })();
  1968.  
  1969. const refresh = (() => {
  1970. const container = content.getElementsByTagName("tbody")[0];
  1971.  
  1972. const func = () => {
  1973. container.innerHTML = "";
  1974.  
  1975. Object.values(witchHunter.data).forEach((item, index) => {
  1976. const tc = document.createElement("tr");
  1977.  
  1978. tc.className = `row${
  1979. (container.querySelectorAll("TR").length % 2) + 1
  1980. }`;
  1981.  
  1982. tc.innerHTML = `
  1983. <td class="c1">
  1984. <div class="filter-input-wrapper">
  1985. <a href="/thread.php?fid=${item.fid}" class="b nobr">[${item.name}]</a>
  1986. </div>
  1987. </td>
  1988. <td class="c2">
  1989. <b class="block_txt nobr" style="background:${item.color}; color:#fff; margin: 0.1em 0.2em;">${item.label}</b>
  1990. </td>
  1991. <td class="c3">
  1992. <div class="filter-table-button-group">
  1993. <button>删除</button>
  1994. </div>
  1995. </td>
  1996. `;
  1997.  
  1998. const actions = tc.getElementsByTagName("button");
  1999.  
  2000. actions[0].onclick = () => {
  2001. if (confirm("是否确认?")) {
  2002. witchHunter.remove(item.id);
  2003.  
  2004. refresh();
  2005. }
  2006. };
  2007.  
  2008. container.appendChild(tc);
  2009. });
  2010.  
  2011. {
  2012. const tc = document.createElement("tr");
  2013.  
  2014. tc.className = `row${
  2015. (container.querySelectorAll("TR").length % 2) + 1
  2016. }`;
  2017.  
  2018. tc.innerHTML = `
  2019. <td class="c1">
  2020. <div class="filter-input-wrapper">
  2021. <input value="" placeholder="版面ID" />
  2022. </div>
  2023. </td>
  2024. <td class="c2">
  2025. <div class="filter-input-wrapper">
  2026. <input value="" />
  2027. </div>
  2028. </td>
  2029. <td class="c3">
  2030. <div class="filter-table-button-group">
  2031. <button>添加</button>
  2032. </div>
  2033. </td>
  2034. `;
  2035.  
  2036. const inputElement = tc.getElementsByTagName("INPUT");
  2037. const actions = tc.getElementsByTagName("button");
  2038.  
  2039. actions[0].onclick = async () => {
  2040. const fid = parseInt(inputElement[0].value, 10);
  2041. const tag = inputElement[1].value.trim();
  2042.  
  2043. if (isNaN(fid) || tag.length === 0) {
  2044. return;
  2045. }
  2046.  
  2047. await witchHunter.add(fid, tag);
  2048.  
  2049. refresh();
  2050. };
  2051.  
  2052. container.appendChild(tc);
  2053. }
  2054. };
  2055.  
  2056. return func;
  2057. })();
  2058.  
  2059. return {
  2060. name: "猎巫",
  2061. content,
  2062. refresh,
  2063. };
  2064. })();
  2065.  
  2066. // 通用设置
  2067. const commonModule = (() => {
  2068. const content = (() => {
  2069. const c = document.createElement("div");
  2070.  
  2071. c.style = "display: none";
  2072.  
  2073. return c;
  2074. })();
  2075.  
  2076. const refresh = (() => {
  2077. const container = content;
  2078.  
  2079. const func = () => {
  2080. container.innerHTML = "";
  2081.  
  2082. // 默认过滤方式
  2083. {
  2084. const tc = document.createElement("div");
  2085.  
  2086. tc.innerHTML += `
  2087. <div>默认过滤方式</div>
  2088. <div></div>
  2089. <div class="silver" style="margin-top: 10px;">${FILTER_TIPS}</div>
  2090. `;
  2091.  
  2092. ["标记", "遮罩", "隐藏"].forEach((item, index) => {
  2093. const ele = document.createElement("SPAN");
  2094.  
  2095. ele.innerHTML += `
  2096. <input id="s-fm-${index}" type="radio" name="filterType" ${
  2097. data.options.filterMode === item && "checked"
  2098. }>
  2099. <label for="s-fm-${index}" style="cursor: pointer;">${item}</label>
  2100. `;
  2101.  
  2102. const inp = ele.querySelector("input");
  2103.  
  2104. inp.onchange = () => {
  2105. if (inp.checked) {
  2106. data.options.filterMode = item;
  2107. saveData();
  2108. reFilter();
  2109. }
  2110. };
  2111.  
  2112. tc.querySelectorAll("div")[1].append(ele);
  2113. });
  2114.  
  2115. container.appendChild(tc);
  2116. }
  2117.  
  2118. // 小号过滤(时间)
  2119. {
  2120. const tc = document.createElement("div");
  2121.  
  2122. tc.innerHTML += `
  2123. <br/>
  2124. <div>
  2125. 隐藏注册时间小于<input value="${
  2126. (data.options.filterRegdateLimit || 0) / 86400000
  2127. }" maxLength="4" style="width: 48px;" />天的用户
  2128. <button>确认</button>
  2129. </div>
  2130. `;
  2131.  
  2132. const actions = tc.getElementsByTagName("button");
  2133.  
  2134. actions[0].onclick = () => {
  2135. const v = actions[0].previousElementSibling.value;
  2136.  
  2137. const n = Number(v) || 0;
  2138.  
  2139. data.options.filterRegdateLimit = n < 0 ? 0 : n * 86400000;
  2140.  
  2141. saveData();
  2142. reFilter();
  2143. };
  2144.  
  2145. container.appendChild(tc);
  2146. }
  2147.  
  2148. // 小号过滤(发帖数)
  2149. {
  2150. const tc = document.createElement("div");
  2151.  
  2152. tc.innerHTML += `
  2153. <br/>
  2154. <div>
  2155. 隐藏发帖数量小于<input value="${
  2156. data.options.filterPostnumLimit || 0
  2157. }" maxLength="5" style="width: 48px;" />贴的用户
  2158. <button>确认</button>
  2159. </div>
  2160. `;
  2161.  
  2162. const actions = tc.getElementsByTagName("button");
  2163.  
  2164. actions[0].onclick = () => {
  2165. const v = actions[0].previousElementSibling.value;
  2166.  
  2167. const n = Number(v) || 0;
  2168.  
  2169. data.options.filterPostnumLimit = n < 0 ? 0 : n;
  2170.  
  2171. saveData();
  2172. reFilter();
  2173. };
  2174.  
  2175. container.appendChild(tc);
  2176. }
  2177.  
  2178. // 声望过滤
  2179. {
  2180. const tc = document.createElement("div");
  2181.  
  2182. tc.innerHTML += `
  2183. <br/>
  2184. <div>
  2185. 隐藏版面声望低于<input value="${
  2186. data.options.filterReputationLimit || ""
  2187. }" maxLength="5" style="width: 48px;" />点的用户
  2188. <button>确认</button>
  2189. </div>
  2190. `;
  2191.  
  2192. const actions = tc.getElementsByTagName("button");
  2193.  
  2194. actions[0].onclick = () => {
  2195. const v = actions[0].previousElementSibling.value;
  2196.  
  2197. const n = Number(v);
  2198.  
  2199. data.options.filterReputationLimit = n;
  2200.  
  2201. saveData();
  2202. reFilter();
  2203. };
  2204.  
  2205. container.appendChild(tc);
  2206. }
  2207.  
  2208. // 删除没有标记的用户
  2209. {
  2210. const tc = document.createElement("div");
  2211.  
  2212. tc.innerHTML += `
  2213. <br/>
  2214. <div>
  2215. <button>删除没有标记的用户</button>
  2216. </div>
  2217. `;
  2218.  
  2219. const actions = tc.getElementsByTagName("button");
  2220.  
  2221. actions[0].onclick = () => {
  2222. if (confirm("是否确认?")) {
  2223. Object.values(data.users).forEach((item) => {
  2224. if (item.tags.length === 0) {
  2225. delete data.users[item.id];
  2226. }
  2227. });
  2228.  
  2229. saveData();
  2230. reFilter();
  2231. }
  2232. };
  2233.  
  2234. container.appendChild(tc);
  2235. }
  2236.  
  2237. // 删除没有用户的标记
  2238. {
  2239. const tc = document.createElement("div");
  2240.  
  2241. tc.innerHTML += `
  2242. <br/>
  2243. <div>
  2244. <button>删除没有用户的标记</button>
  2245. </div>
  2246. `;
  2247.  
  2248. const actions = tc.getElementsByTagName("button");
  2249.  
  2250. actions[0].onclick = () => {
  2251. if (confirm("是否确认?")) {
  2252. Object.values(data.tags).forEach((item) => {
  2253. if (
  2254. Object.values(data.users).filter((user) =>
  2255. user.tags.find((tag) => tag === item.id)
  2256. ).length === 0
  2257. ) {
  2258. delete data.tags[item.id];
  2259. }
  2260. });
  2261.  
  2262. saveData();
  2263. reFilter();
  2264. }
  2265. };
  2266.  
  2267. container.appendChild(tc);
  2268. }
  2269. };
  2270.  
  2271. return func;
  2272. })();
  2273.  
  2274. return {
  2275. name: "通用设置",
  2276. content,
  2277. refresh,
  2278. };
  2279. })();
  2280.  
  2281. u.addModule(userModule).toggle();
  2282. u.addModule(tagModule);
  2283. u.addModule(keywordModule);
  2284. u.addModule(locationModule);
  2285. u.addModule(witchHuntModule);
  2286. u.addModule(commonModule);
  2287.  
  2288. // 增加菜单项
  2289. (() => {
  2290. const title = "过滤设置";
  2291.  
  2292. let window;
  2293.  
  2294. const container = document.createElement("DIV");
  2295.  
  2296. container.className = `td`;
  2297. container.innerHTML = `<a class="mmdefault" href="javascript: void(0);" style="white-space: nowrap;">屏蔽</a>`;
  2298.  
  2299. const content = container.querySelector("A");
  2300.  
  2301. const anchor = document.querySelector("#mainmenu .td:last-child");
  2302.  
  2303. anchor.before(container);
  2304.  
  2305. content.onclick = () => {
  2306. if (window === undefined) {
  2307. window = n.createCommmonWindow();
  2308. }
  2309.  
  2310. window._.addContent(null);
  2311. window._.addTitle(title);
  2312. window._.addContent(u.content);
  2313. window._.show();
  2314. };
  2315. })();
  2316.  
  2317. // 执行过滤
  2318. (() => {
  2319. const hookFunction = (object, functionName, callback) => {
  2320. ((originalFunction) => {
  2321. object[functionName] = function () {
  2322. const returnValue = originalFunction.apply(this, arguments);
  2323.  
  2324. callback.apply(this, [returnValue, originalFunction, arguments]);
  2325.  
  2326. return returnValue;
  2327. };
  2328. })(object[functionName]);
  2329. };
  2330.  
  2331. const initialized = {
  2332. topicArg: false,
  2333. postArg: false,
  2334. };
  2335.  
  2336. hookFunction(n, "eval", () => {
  2337. if (Object.values(initialized).findIndex((item) => item === false) < 0) {
  2338. return;
  2339. }
  2340.  
  2341. if (n.topicArg && initialized.topicArg === false) {
  2342. hookFunction(n.topicArg, "add", reFilter);
  2343.  
  2344. initialized.topicArg = true;
  2345. }
  2346.  
  2347. if (n.postArg && initialized.postArg === false) {
  2348. hookFunction(n.postArg, "proc", reFilter);
  2349.  
  2350. initialized.postArg = true;
  2351. }
  2352. });
  2353.  
  2354. reFilter();
  2355. })();
  2356. })(commonui, __CURRENT_UID);