NGA Filter

NGA 屏蔽插件,支持用户、标记、关键字、属地、小号、流量号、低声望、匿名过滤。troll must die。

当前为 2023-12-28 提交的版本,查看 最新版本

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址