NGA Filter

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

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        NGA Filter
// @namespace   https://greasyfork.org/users/263018
// @version     1.12.1
// @author      snyssss
// @description NGA 屏蔽插件,支持用户、标记、关键字、属地、小号、流量号、低声望、匿名过滤。troll must die。
// @license     MIT

// @match       *://bbs.nga.cn/*
// @match       *://ngabbs.com/*
// @match       *://nga.178.com/*

// @grant       GM_addStyle
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_registerMenuCommand

// @noframes
// ==/UserScript==

((n, self) => {
  if (n === undefined) return;

  // KEY
  const DATA_KEY = "NGAFilter";
  const USER_AGENT_KEY = "USER_AGENT_KEY";

  // User Agent
  const USER_AGENT = (() => {
    const data = GM_getValue(USER_AGENT_KEY) || "Nga_Official";

    GM_registerMenuCommand(`修改UA:${data}`, () => {
      const value = prompt("修改UA", data);

      if (value) {
        GM_setValue(USER_AGENT_KEY, value);

        location.reload();
      }
    });

    return data;
  })();

  // 简单的统一请求
  const request = (url, config = {}) =>
    fetch(url, {
      headers: {
        "X-User-Agent": USER_AGENT,
      },
      ...config,
    });

  // 过滤提示
  const FILTER_TIPS =
    "过滤顺序:用户 &gt; 标记 &gt; 关键字 &gt; 属地<br/>过滤级别:隐藏 &gt; 遮罩 &gt; 标记 &gt; 继承 &gt; 显示<br/>相同类型按最高级别过滤";

  // 过滤方式
  const FILTER_MODE = ["继承", "标记", "遮罩", "隐藏", "显示"];

  // 切换过滤方式
  const switchFilterMode = (value) => {
    const next = FILTER_MODE.indexOf(value) + 1;

    if (next >= FILTER_MODE.length) {
      return FILTER_MODE[0];
    }

    return FILTER_MODE[next];
  };

  // 数据
  const data = (() => {
    const d = {
      tags: {},
      users: {},
      keywords: {},
      locations: {},
      options: {
        filterRegdateLimit: 0,
        filterPostnumLimit: 0,
        filterTopicRateLimit: 100,
        filterReputationLimit: NaN,
        filterAnony: false,
        filterMode: "隐藏",
      },
    };

    const v = GM_getValue(DATA_KEY);

    if (typeof v !== "object") {
      return d;
    }

    return Object.assign(d, v);
  })();

  // 保存数据
  const saveData = () => {
    GM_setValue(DATA_KEY, data);
  };

  // 增加标记
  const addTag = (name) => {
    const tag = Object.values(data.tags).find((item) => item.name === name);

    if (tag) return tag.id;

    const id =
      Math.max(...Object.values(data.tags).map((item) => item.id), 0) + 1;

    const hash = (() => {
      let h = 5381;
      for (var i = 0; i < name.length; i++) {
        h = ((h << 5) + h + name.charCodeAt(i)) & 0xffffffff;
      }
      return h;
    })();

    const hex = Math.abs(hash).toString(16) + "000000";

    const hsv = [
      `0x${hex.substring(2, 4)}` / 255,
      `0x${hex.substring(2, 4)}` / 255 / 2 + 0.25,
      `0x${hex.substring(4, 6)}` / 255 / 2 + 0.25,
    ];

    const rgb = n.hsvToRgb(hsv[0], hsv[1], hsv[2]);

    const color = ["#", ...rgb].reduce((a, b) => {
      return a + ("0" + b.toString(16)).slice(-2);
    });

    data.tags[id] = {
      id,
      name,
      color,
      filterMode: FILTER_MODE[0],
    };

    saveData();

    return id;
  };

  // 增加用户
  const addUser = (id, name = null, tags = [], filterMode = FILTER_MODE[0]) => {
    if (data.users[id]) return data.users[id];

    data.users[id] = {
      id,
      name,
      tags,
      filterMode,
    };

    saveData();

    return data.users[id];
  };

  // 增加关键字
  const addKeyword = (
    keyword,
    filterMode = FILTER_MODE[0],
    filterLevel = 0
  ) => {
    const id =
      Math.max(...Object.values(data.keywords).map((item) => item.id), 0) + 1;

    data.keywords[id] = {
      id,
      keyword,
      filterMode,
      filterLevel,
    };

    saveData();

    return id;
  };

  // 增加属地
  const addLocation = (keyword, filterMode = FILTER_MODE[0]) => {
    const id =
      Math.max(...Object.values(data.locations).map((item) => item.id), 0) + 1;

    data.locations[id] = {
      id,
      keyword,
      filterMode,
    };

    saveData();

    return id;
  };

  // 旧版本数据迁移
  {
    const dataKey = "troll_data";
    const modeKey = "troll_mode";
    const keywordKey = "troll_keyword";

    if (localStorage.getItem(dataKey)) {
      let trollMap = (function () {
        try {
          return JSON.parse(localStorage.getItem(dataKey)) || {};
        } catch (e) {}

        return {};
      })();

      let filterMode = ~~localStorage.getItem(modeKey);

      let filterKeyword = localStorage.getItem(keywordKey) || "";

      // 整理标签
      [...new Set(Object.values(trollMap).flat())].forEach((item) =>
        addTag(item)
      );

      // 整理用户
      Object.keys(trollMap).forEach((item) => {
        addUser(
          item,
          null,
          (typeof trollMap[item] === "object" ? trollMap[item] : []).map(
            (tag) => addTag(tag)
          )
        );
      });

      data.options.filterMode = filterMode ? "隐藏" : "标记";
      data.options.keyword = filterKeyword;

      localStorage.removeItem(dataKey);
      localStorage.removeItem(modeKey);
      localStorage.removeItem(keywordKey);

      saveData();
    }

    // v1.1.0 -> v1.1.1
    {
      Object.values(data.users).forEach(({ id, name, tags, enabled }) => {
        if (enabled !== undefined) {
          data.users[id] = {
            id,
            name,
            tags,
            filterMode: enabled ? "继承" : "显示",
          };
        }
      });

      Object.values(data.tags).forEach(({ id, name, color, enabled }) => {
        if (enabled !== undefined) {
          data.tags[id] = {
            id,
            name,
            color,
            filterMode: enabled ? "继承" : "显示",
          };
        }
      });

      if (data.options.filterMode === 0) {
        data.options.filterMode = "隐藏";
      } else if (data.options.filterMode === 1) {
        data.options.filterMode = "标记";
      }

      saveData();
    }

    // v1.2.x -> v1.3.0
    {
      if (data.options.keyword) {
        addKeyword(data.options.keyword);

        delete data.options.keyword;

        saveData();
      }
    }
  }

  // 编辑用户标记
  const editUser = (() => {
    let window;
    return (uid, name, callback) => {
      if (window === undefined) {
        window = n.createCommmonWindow();
      }

      const user = data.users[uid];

      const content = document.createElement("div");

      const size = Math.floor((screen.width * 0.8) / 200);

      const items = Object.values(data.tags).map(
        (tag, index) => `
          <td class="c1">
            <label for="s-tag-${index}" style="display: block; cursor: pointer;">
              <b class="block_txt nobr" style="background:${
                tag.color
              }; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>
            </label>
          </td>
          <td class="c2" width="1">
              <input id="s-tag-${index}" type="checkbox" value="${tag.id}" ${
          user && user.tags.find((item) => item === tag.id) && "checked"
        }/>
          </td>
        `
      );

      const rows = [...new Array(Math.ceil(items.length / size))].map(
        (item, index) =>
          `
          <tr class="row${(index % 2) + 1}">
            ${items.slice(size * index, size * (index + 1)).join("")}
          </tr>
          `
      );

      content.className = "w100";
      content.innerHTML = `
        <div class="filter-table-wrapper" style="width: 80vw;">
          <table class="filter-table forumbox">
            <tbody>
              ${rows.join("")}
            </tbody>
          </table>
        </div>
        <div style="margin: 10px 0;">
            <input placeholder="一次性添加多个标记用&quot;|&quot;隔开,不会添加重名标记" style="width: -webkit-fill-available;" />
        </div>
        <div style="margin: 10px 0;">
            <span>过滤方式:</span>
            <button>${(user && user.filterMode) || FILTER_MODE[0]}</button>
            <div class="right_">
                <button>删除</button>
                <button>保存</button>
            </div>
        </div>
        <div class="silver" style="margin-top: 5px;">${FILTER_TIPS}</div>
    `;

      const actions = content.getElementsByTagName("button");

      actions[0].onclick = () => {
        actions[0].innerText = switchFilterMode(
          actions[0].innerText || FILTER_MODE[0]
        );
      };

      actions[1].onclick = () => {
        if (confirm("是否确认?")) {
          delete data.users[uid];

          saveData();

          callback && callback();

          window._.hide();
        }
      };

      actions[2].onclick = () => {
        if (confirm("是否确认?")) {
          const values = [...content.getElementsByTagName("input")];
          const newTags = values[values.length - 1].value
            .split("|")
            .filter((item) => item.length)
            .map((item) => addTag(item));
          const tags = [
            ...new Set(
              values
                .filter((item) => item.type === "checkbox" && item.checked)
                .map((item) => ~~item.value)
                .concat(newTags)
            ),
          ].sort();

          if (user) {
            user.tags = tags;
            user.filterMode = actions[0].innerText;
          } else {
            addUser(uid, name, tags, actions[0].innerText);
          }

          saveData();

          callback && callback();

          window._.hide();
        }
      };

      if (user === undefined) {
        actions[1].style = "display: none;";
      }

      window._.addContent(null);
      window._.addTitle(`编辑标记 - ${name ? name : "#" + uid}`);
      window._.addContent(content);
      window._.show();
    };
  })();

  // 猎巫
  const witchHunter = (() => {
    const key = "WITCH_HUNTER";

    const data = GM_getValue(key) || {};

    const add = async (fid, label) => {
      if (Object.values(data).find((item) => item.fid === fid)) {
        alert("已有相同版面ID");
        return;
      }

      const info = await new Promise((resolve) => {
        request(`/thread.php?lite=js&fid=${fid}`)
          .then((res) => res.blob())
          .then((blob) => {
            const reader = new FileReader();

            reader.onload = () => {
              const text = reader.result;
              const result = JSON.parse(
                text.replace("window.script_muti_get_var_store=", "")
              );

              resolve(result.data);
            };

            reader.readAsText(blob, "GBK");
          })
          .catch(() => {
            resolve({});
          });
      });

      if (info.__F === undefined) {
        alert("版面ID有误");
        return;
      }

      const name = info.__F.name;

      const id = Math.max(...Object.values(data).map((item) => item.id), 0) + 1;

      const hash = (() => {
        let h = 5381;
        for (var i = 0; i < label.length; i++) {
          h = ((h << 5) + h + label.charCodeAt(i)) & 0xffffffff;
        }
        return h;
      })();

      const hex = Math.abs(hash).toString(16) + "000000";

      const hsv = [
        `0x${hex.substring(2, 4)}` / 255,
        `0x${hex.substring(2, 4)}` / 255 / 2 + 0.25,
        `0x${hex.substring(4, 6)}` / 255 / 2 + 0.25,
      ];

      const rgb = n.hsvToRgb(hsv[0], hsv[1], hsv[2]);

      const color = ["#", ...rgb].reduce((a, b) => {
        return a + ("0" + b.toString(16)).slice(-2);
      });

      data[id] = {
        id,
        fid,
        name,
        label,
        color,
      };

      GM_setValue(key, data);
    };

    const remove = (id) => {
      delete data[id];

      GM_setValue(key, data);
    };

    const run = (uid, element) => {
      if (uid < 0) {
        return;
      }

      Promise.all(
        Object.values(data).map(async (item) => {
          const api = `/thread.php?lite=js&fid=${item.fid}&authorid=${uid}`;

          const verify =
            (await new Promise((resolve) => {
              request(api)
                .then((res) => res.blob())
                .then((blob) => {
                  const reader = new FileReader();

                  reader.onload = () => {
                    const text = reader.result;
                    const result = JSON.parse(
                      text.replace("window.script_muti_get_var_store=", "")
                    );

                    if (result.error) {
                      resolve(false);
                      return;
                    }

                    resolve(true);
                  };

                  reader.readAsText(blob, "GBK");
                })
                .catch(() => {
                  resolve(false);
                });
            })) ||
            (await new Promise((resolve) => {
              request(`${api}&searchpost=1`)
                .then((res) => res.blob())
                .then((blob) => {
                  const reader = new FileReader();

                  reader.onload = () => {
                    const text = reader.result;
                    const result = JSON.parse(
                      text.replace("window.script_muti_get_var_store=", "")
                    );

                    if (result.error) {
                      resolve(false);
                      return;
                    }

                    resolve(true);
                  };

                  reader.readAsText(blob, "GBK");
                })
                .catch(() => {
                  resolve(false);
                });
            }));

          if (verify) {
            return item;
          }
        })
      )
        .then((res) => res.filter((item) => item))
        .then((res) => {
          res
            .filter(
              (current, index) =>
                res.findIndex((item) => item.label === current.label) === index
            )
            .forEach((item) => {
              element.style.display = "block";
              element.innerHTML += `<b class="block_txt nobr" style="background:${item.color}; color:#fff; margin: 0.1em 0.2em;">${item.label}</b>`;
            });
        });
    };

    return {
      add,
      remove,
      run,
      data,
    };
  })();

  // 获取主题数量
  const getTopicNum = async (uid) => {
    const api = `/thread.php?lite=js&authorid=${uid}`;

    const { __ROWS } = await new Promise((resolve) => {
      request(api)
        .then((res) => res.blob())
        .then((blob) => {
          const reader = new FileReader();

          reader.onload = () => {
            const text = reader.result;
            const result = JSON.parse(
              text.replace("window.script_muti_get_var_store=", "")
            );

            resolve(result.data);
          };

          reader.readAsText(blob, "GBK");
        })
        .catch(() => {
          resolve({});
        });
    });

    return __ROWS;
  };

  // 小号过滤和声望过滤、流量号过滤
  const getFilterModeByUserInfo = async (
    userInfo,
    reputation,
    subject,
    content
  ) => {
    const filterRegdateLimit = data.options.filterRegdateLimit || 0;

    const filterPostnumLimit = data.options.filterPostnumLimit || 0;

    const filterTopicRateLimit = data.options.filterTopicRateLimit || 100;

    const filterReputationLimit = data.options.filterReputationLimit || NaN;

    if (userInfo) {
      const { uid, username, regdate, postnum } = userInfo;

      if (
        filterRegdateLimit > 0 &&
        regdate * 1000 > new Date() - filterRegdateLimit
      ) {
        m.add({
          user: `<a href="${
            uid > 0 ? `/nuke.php?func=ucp&uid=${uid}` : `javascript:void(0)`
          }" class="b nobr">[${username ? "@" + username : "#" + uid}]</a>`,
          mode: "隐藏",
          subject,
          content,
          reason: `注册时间: ${new Date(regdate * 1000).toLocaleDateString()}`,
        });

        return true;
      }

      if (filterPostnumLimit > 0 && postnum < filterPostnumLimit) {
        m.add({
          user: `<a href="${
            uid > 0 ? `/nuke.php?func=ucp&uid=${uid}` : `javascript:void(0)`
          }" class="b nobr">[${username ? "@" + username : "#" + uid}]</a>`,
          mode: "隐藏",
          subject,
          content,
          reason: `发帖数量: ${postnum}`,
        });

        return true;
      }

      if (filterTopicRateLimit > 0 && filterTopicRateLimit < 100) {
        const topicNum = await getTopicNum(uid);

        const topicRate = (topicNum / postnum) * 100;

        if (topicRate > filterTopicRateLimit) {
          m.add({
            user: `<a href="${
              uid > 0 ? `/nuke.php?func=ucp&uid=${uid}` : `javascript:void(0)`
            }" class="b nobr">[${username ? "@" + username : "#" + uid}]</a>`,
            mode: "隐藏",
            subject,
            content,
            reason: `发帖比例: ${topicRate.toFixed(0)}%`,
          });

          return true;
        }
      }
    }

    if (Number.isNaN(filterReputationLimit) === false) {
      if (reputation < filterReputationLimit) {
        const { uid, username } = userInfo;

        m.add({
          user: `<a href="${
            uid > 0 ? `/nuke.php?func=ucp&uid=${uid}` : `javascript:void(0)`
          }" class="b nobr">[${username ? "@" + username : "#" + uid}]</a>`,
          mode: "隐藏",
          subject,
          content,
          reason: `声望: ${reputation}`,
        });

        return true;
      }
    }

    return false;
  };

  // 判断过滤方式
  const getFilterMode = async (uid, subject, content) => {
    let result = -1;

    const user = data.users[uid];

    const tags = user ? user.tags.map((tag) => data.tags[tag]) : [];

    const keywords = Object.values(data.keywords);

    const locations = Object.values(data.locations);

    if (uid > 0) {
      const userInfo = n.userInfo.users[uid];

      const reputation = (() => {
        const reputations = n.userInfo.reputations;

        if (reputations) {
          for (let fid in reputations) {
            return reputations[fid][uid] || 0;
          }
        }

        return NaN;
      })();

      if (
        await getFilterModeByUserInfo(userInfo, reputation, subject, content)
      ) {
        return FILTER_MODE.indexOf("隐藏");
      }
    } else if (uid < 0 && data.options.filterAnony) {
      m.add({
        user: `<a href="javascript:void(0)" class="b nobr">[匿名]</a>`,
        mode: "隐藏",
        subject,
        content,
        reason: `匿名`,
      });

      return FILTER_MODE.indexOf("隐藏");
    }

    if (user) {
      const filterMode = FILTER_MODE.indexOf(user.filterMode);

      if (filterMode > 0) {
        m.add({
          user: `<a href="/nuke.php?func=ucp&uid=${user.id}" class="b nobr">[${
            user.name ? "@" + user.name : "#" + user.id
          }]</a>`,
          mode: FILTER_MODE[filterMode],
          subject,
          content,
          reason: `用户模式: ${FILTER_MODE[filterMode]}`,
        });

        return filterMode;
      }

      result = filterMode;
    }

    if (tags.length) {
      const filterMode = (() => {
        if (tags.some((tag) => tag.filterMode !== "显示")) {
          return tags
            .filter((tag) => tag.filterMode !== "显示")
            .map((tag) => FILTER_MODE.indexOf(tag.filterMode) || 0)
            .sort((a, b) => b - a)[0];
        }

        return FILTER_MODE.indexOf("显示");
      })();

      if (filterMode > 0) {
        m.add({
          user: `<a href="/nuke.php?func=ucp&uid=${user.id}" class="b nobr">[${
            user.name ? "@" + user.name : "#" + user.id
          }]</a>`,
          mode: FILTER_MODE[filterMode],
          subject,
          content,
          reason: `标记模式: ${FILTER_MODE[filterMode]}`,
        });

        return filterMode;
      }

      result = filterMode;
    }

    if (keywords.length) {
      const filterMode = (() => {
        const sR = (() => {
          if (subject) {
            const r = keywords
              .filter((item) => item.keyword && item.filterMode !== "显示")
              .filter((item) => (item.filterLevel || 0) >= 0)
              .sort(
                (a, b) =>
                  FILTER_MODE.indexOf(b.filterMode) -
                  FILTER_MODE.indexOf(a.filterMode)
              )
              .find((item) => subject.search(item.keyword) >= 0);

            if (r) {
              if (uid > 0) {
                const { username } = n.userInfo.users[uid];

                m.add({
                  user: `<a href="/nuke.php?func=ucp&uid=${uid}" class="b nobr">[@${username}]</a>`,
                  mode: r.keyword,
                  subject,
                  content,
                  reason: `关键词模式: ${r.keyword}`,
                });
              } else {
                m.add({
                  user: `<a href="javascript:void(0)" class="b nobr">[匿名]</a>`,
                  mode: r.keyword,
                  subject,
                  content,
                  reason: `关键词模式: ${r.keyword}`,
                });
              }

              return FILTER_MODE.indexOf(r.filterMode);
            }
          }

          return -1;
        })();

        const cR = (() => {
          if (content) {
            const r = keywords
              .filter((item) => item.keyword && item.filterMode !== "显示")
              .filter((item) => (item.filterLevel || 0) >= 1)
              .sort(
                (a, b) =>
                  FILTER_MODE.indexOf(b.filterMode) -
                  FILTER_MODE.indexOf(a.filterMode)
              )
              .find((item) => content.search(item.keyword) >= 0);

            if (r) {
              if (uid > 0) {
                const { username } = n.userInfo.users[uid];

                m.add({
                  user: `<a href="/nuke.php?func=ucp&uid=${uid}" class="b nobr">[@${username}]</a>`,
                  mode: r.filterMode,
                  subject,
                  content,
                  reason: `关键词模式: ${r.filterMode}`,
                });
              } else {
                m.add({
                  user: `<a href="javascript:void(0)" class="b nobr">[匿名]</a>`,
                  mode: r.filterMode,
                  subject,
                  content,
                  reason: `关键词模式: ${r.filterMode}`,
                });
              }

              return FILTER_MODE.indexOf(r.filterMode);
            }
          }

          return -1;
        })();

        return Math.max(sR, cR, result);
      })();

      if (filterMode > 0) {
        return filterMode;
      }

      result = filterMode;
    }

    if (locations.length) {
      const { ipLoc } = await new Promise((resolve) => {
        request(`/nuke.php?lite=js&__lib=ucp&__act=get&uid=${uid}`)
          .then((res) => res.blob())
          .then((blob) => {
            const reader = new FileReader();

            reader.onload = () => {
              const text = reader.result;
              const result = JSON.parse(
                text.replace("window.script_muti_get_var_store=", "")
              );

              resolve(result.data[0]);
            };

            reader.readAsText(blob, "GBK");
          })
          .catch(() => {
            resolve({});
          });
      });

      if (ipLoc) {
        const filterMode = (() => {
          const r = locations
            .filter((item) => item.keyword && item.filterMode !== "显示")
            .sort(
              (a, b) =>
                FILTER_MODE.indexOf(b.filterMode) -
                FILTER_MODE.indexOf(a.filterMode)
            )
            .find((item) => ipLoc.search(item.keyword) >= 0);

          if (r) {
            const { username } = n.userInfo.users[uid];

            m.add({
              user: `<a href="/nuke.php?func=ucp&uid=${uid}" class="b nobr">[@${username}]</a>`,
              mode: r.filterMode,
              subject,
              content,
              reason: `属地模式: ${r.filterMode}`,
            });

            return FILTER_MODE.indexOf(r.filterMode);
          }

          return Math.max(r, result);
        })();

        if (filterMode > 0) {
          return filterMode;
        }

        result = filterMode;
      }
    }

    return result;
  };

  // 根据 TID 获取过滤方式
  const getFilterModeByTopic = async (tid) => {
    return await new Promise((resolve, reject) => {
      const api = `/read.php?tid=${tid}`;

      request(api)
        .then((res) => res.blob())
        .then((blob) => {
          const getLastIndex = (content, position) => {
            if (position >= 0) {
              let nextIndex = position + 1;

              while (nextIndex < content.length) {
                if (content[nextIndex] === "}") {
                  return nextIndex;
                }

                if (content[nextIndex] === "{") {
                  nextIndex = getLastIndex(content, nextIndex);

                  if (nextIndex < 0) {
                    break;
                  }
                }

                nextIndex = nextIndex + 1;
              }
            }

            return -1;
          };

          const reader = new FileReader();

          reader.onload = async () => {
            const parser = new DOMParser();

            const doc = parser.parseFromString(reader.result, "text/html");

            const html = doc.body.innerHTML;

            // 验证帖子正常
            const verify = doc.querySelector("#m_posts");

            if (verify) {
              // 取得顶楼 UID
              const uid = (() => {
                const ele = doc.querySelector("#postauthor0");

                if (ele) {
                  const res = ele.getAttribute("href").match(/uid=(\S+)/);

                  if (res) {
                    return res[1];
                  }
                }

                return 0;
              })();

              // 取得顶楼标题
              const subject = doc.querySelector("#postsubject0").innerHTML;

              // 取得顶楼内容
              const content = doc.querySelector("#postcontent0").innerHTML;

              if (uid && uid > 0) {
                // 取得用户信息
                const userInfo = (() => {
                  // 起始JSON
                  const str = `"${uid}":{`;

                  // 起始下标
                  const index = html.indexOf(str) + str.length;

                  // 结尾下标
                  const lastIndex = getLastIndex(html, index);

                  if (lastIndex >= 0) {
                    try {
                      return JSON.parse(
                        `{${html.substring(index, lastIndex)}}`
                      );
                    } catch {}
                  }

                  return null;
                })();

                // 取得用户声望
                const reputation = (() => {
                  const reputations = (() => {
                    // 起始JSON
                    const str = `"__REPUTATIONS":{`;

                    // 起始下标
                    const index = html.indexOf(str) + str.length;

                    // 结尾下标
                    const lastIndex = getLastIndex(html, index);

                    if (lastIndex >= 0) {
                      return JSON.parse(
                        `{${html.substring(index, lastIndex)}}`
                      );
                    }

                    return null;
                  })();

                  if (reputations) {
                    for (let fid in reputations) {
                      return reputations[fid][uid] || 0;
                    }
                  }

                  return NaN;
                })();

                if (
                  await getFilterModeByUserInfo(
                    userInfo,
                    reputation,
                    subject,
                    content
                  )
                ) {
                  resolve(FILTER_MODE.indexOf("隐藏"));
                }
              }

              resolve(getFilterMode(uid, subject, content));
            } else {
              reject();
            }
          };

          reader.readAsText(blob, "GBK");
        })
        .catch(() => {
          reject();
        });
    }).catch(() => {
      return FILTER_MODE.indexOf("隐藏");
    });
  };

  // 处理引用
  const handleQuote = async (content) => {
    const quotes = content.querySelectorAll(".quote");

    await Promise.all(
      [...quotes].map(async (quote) => {
        const uid = (() => {
          const ele = quote.querySelector("a[href^='/nuke.php']");

          if (ele) {
            const res = ele.getAttribute("href").match(/uid=(\S+)/);

            if (res) {
              return res[1];
            }
          }

          return 0;
        })();

        const filterMode = await new Promise(async (resolve) => {
          const mode = await getFilterMode(uid, "", quote.innerText);

          if (mode === 0) {
            resolve(data.options.filterMode);
          }

          if (mode > 0) {
            resolve(FILTER_MODE[mode]);
          }

          resolve("");
        });

        if (filterMode === "标记") {
          quote.innerHTML = `
          <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
              <span class="crimson">Troll must die.</span>
              <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
              <div style="display: none;" name="troll_${uid}">
                  ${quote.innerHTML}
              </div>
          </div>`;
        } else if (filterMode === "遮罩") {
          const source = document.createElement("DIV");

          source.innerHTML = quote.innerHTML;
          source.style.display = "none";

          const caption = document.createElement("CAPTION");

          caption.className = "filter-mask filter-mask-block";

          caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
          caption.onclick = () => {
            quote.removeChild(caption);

            source.style.display = "";
          };

          quote.innerHTML = "";
          quote.appendChild(source);
          quote.appendChild(caption);
        } else if (filterMode === "隐藏") {
          quote.innerHTML = "";
        }
      })
    );
  };

  // 过滤
  const reFilter = (() => {
    let hasNext = false;
    let isRunning = false;

    const func = async () => {
      const tPage = location.pathname === "/thread.php";
      const pPage = location.pathname === "/read.php";

      if (tPage) {
        const params = new URLSearchParams(location.search);

        if (params.has("favor")) {
          return;
        }

        if (params.has("authorid")) {
          return;
        }
      }

      if (tPage) {
        const tData = n.topicArg.data;

        await Promise.all(
          Object.values(tData).map(async (item) => {
            if (item.containerC) return;

            const tid = item[8];

            const filterMode = await new Promise(async (resolve) => {
              const mode = await getFilterModeByTopic(tid);

              if (mode === 0) {
                resolve(data.options.filterMode);
              }

              if (mode > 0) {
                resolve(FILTER_MODE[mode]);
              }

              resolve("");
            });

            item.contentC = item[1];

            item.contentB = item.contentB || item.contentC.innerHTML;

            item.containerC =
              item.containerC || item.contentC.parentNode.parentNode;

            item.containerC.style = "";
            item.contentC.style = "";
            item[1].className = item[1].className.replace(" filter-mask", "");
            item[2].className = item[2].className.replace(" filter-mask", "");

            if (filterMode === "标记") {
              item.contentC.style = "text-decoration: line-through;";
            } else if (filterMode === "遮罩") {
              item[1].className += " filter-mask";
              item[2].className += " filter-mask";
            } else if (filterMode === "隐藏") {
              item.containerC.style = "display: none;";
            }
          })
        );
      } else if (pPage) {
        const pData = n.postArg.data;

        await Promise.all(
          Object.values(pData).map(async (item) => {
            const uid = ~~item.pAid;

            if (uid === self) return;
            if (item.containerC) return;

            if (typeof item.i === "number") {
              item.actionC =
                item.actionC ||
                (() => {
                  const container =
                    item.uInfoC
                      .closest("tr")
                      .querySelector(".posterInfoLine") ||
                    item.uInfoC.querySelector("div");

                  const ele = container.querySelector('[name="uid"]');

                  if (ele) {
                    ele.innerHTML = `屏蔽`;
                    ele.onclick = null;

                    return ele;
                  }

                  const anchor = container.querySelector(".author ~ br");

                  if (anchor) {
                    const btn = document.createElement("A");

                    btn.name = "uid";
                    btn.href = "javascript:void(0)";
                    btn.className =
                      "small_colored_text_btn stxt block_txt_c0 vertmod";
                    btn.innerHTML = `屏蔽`;

                    anchor.parentNode.insertBefore(btn, anchor);

                    return btn;
                  }
                })();

              item.tagC =
                item.tagC ||
                (() => {
                  const container =
                    item.uInfoC
                      .closest("tr")
                      .querySelector(".posterInfoLine") || item.uInfoC;

                  const tc = document.createElement("div");

                  tc.className = "filter-tags";

                  container.appendChild(tc);

                  return tc;
                })();
            }

            item.pName =
              item.pName ||
              (() => {
                const container =
                  item.uInfoC.closest("tr").querySelector(".posterInfoLine") ||
                  item.uInfoC;

                return container.querySelector(".author").innerText;
              })();

            item.reFilter =
              item.reFilter ||
              (async () => {
                const filterMode = await new Promise(async (resolve) => {
                  const mode = await getFilterMode(
                    uid,
                    item.subjectC.innerText,
                    item.contentC.innerText
                  );

                  if (mode === 0) {
                    resolve(data.options.filterMode);
                  }

                  if (mode > 0) {
                    resolve(FILTER_MODE[mode]);
                  }

                  resolve("");
                });

                item.avatarC =
                  item.avatarC ||
                  (() => {
                    const tc = document.createElement("div");

                    const avatar = document.getElementById(
                      `posteravatar${item.i}`
                    );

                    if (avatar) {
                      avatar.parentNode.insertBefore(tc, avatar.nextSibling);

                      tc.appendChild(avatar);
                    }

                    return tc;
                  })();

                item.contentB = item.contentB || item.contentC.innerHTML;

                item.containerC =
                  item.containerC ||
                  (() => {
                    let temp = item.contentC;

                    if (item.i >= 0) {
                      while (temp.nodeName !== "TBODY") {
                        temp = temp.parentNode;
                      }
                    } else {
                      while (temp.nodeName !== "DIV") {
                        temp = temp.parentNode;
                      }
                    }

                    return temp;
                  })();

                item.avatarC.style.display = "";
                item.containerC.style.display = "";
                item.contentC.innerHTML = item.contentB;

                if (item.actionC) {
                  item.actionC.style = "background: #aaa;";

                  if (uid < 0) {
                    item.actionC.style.display = "none";
                  }
                }

                if (filterMode === "标记") {
                  item.avatarC.style.display = "none";
                  item.contentC.innerHTML = `
                <div class="lessernuke" style="background: #81C7D4; border-color: #66BAB7; ">
                    <span class="crimson">Troll must die.</span>
                    <a href="javascript:void(0)" onclick="[...document.getElementsByName('troll_${uid}')].forEach(item => item.style.display = '')">点击查看</a>
                    <div style="display: none;" name="troll_${uid}">
                        ${item.contentB}
                    </div>
                </div>`;

                  if (item.actionC && data.users[uid]) {
                    item.actionC.style = "background: #cb4042;";
                  }
                } else if (filterMode === "遮罩") {
                  const caption = document.createElement("CAPTION");

                  if (item.i >= 0) {
                    caption.className = "filter-mask filter-mask-block";
                  } else {
                    caption.className = "filter-mask filter-mask-block left";
                    caption.style = "width: 47%;";
                  }

                  caption.innerHTML = `<span class="crimson">Troll must die.</span>`;
                  caption.onclick = () => {
                    item.containerC.parentNode.removeChild(caption);
                    item.containerC.style.display = "";
                  };

                  item.containerC.parentNode.insertBefore(
                    caption,
                    item.containerC
                  );
                  item.containerC.style.display = "none";

                  if (item.actionC && data.users[uid]) {
                    item.actionC.style = "background: #cb4042;";
                  }
                } else if (filterMode === "隐藏") {
                  item.containerC.style.display = "none";
                } else {
                  await handleQuote(item.contentC);
                }

                if (item.tagC) {
                  const tags = data.users[uid]
                    ? data.users[uid].tags.map((tag) => data.tags[tag]) || []
                    : [];

                  item.tagC.style.display = tags.length ? "" : "none";
                  item.tagC.innerHTML = tags
                    .map(
                      (tag) =>
                        `<b class="block_txt nobr" style="background:${tag.color}; color:#fff; margin: 0.1em 0.2em;">${tag.name}</b>`
                    )
                    .join("");

                  witchHunter.run(uid, item.tagC);
                }
              });

            if (item.actionC) {
              item.actionC.onclick =
                item.actionC.onclick ||
                ((e) => {
                  if (item.pAid < 0) return;

                  const user = data.users[item.pAid];

                  if (e.ctrlKey === false) {
                    editUser(item.pAid, item.pName, item.reFilter);
                  } else {
                    if (user) {
                      delete data.users[user.id];
                    } else {
                      addUser(item.pAid, item.pName);
                    }

                    saveData();
                    item.reFilter();
                  }
                });
            }

            await item.reFilter();
          })
        );
      }
    };

    const execute = () =>
      func().finally(() => {
        if (hasNext) {
          hasNext = false;

          execute();
        } else {
          isRunning = false;
        }
      });

    return async () => {
      if (isRunning) {
        hasNext = true;
      } else {
        isRunning = true;

        await execute();
      }
    };
  })();

  // STYLE
  GM_addStyle(`
    .filter-table-wrapper {
        max-height: 80vh;
        overflow-y: auto;
    }
    .filter-table {
        margin: 0;
    }
    .filter-table th,
    .filter-table td {
        position: relative;
        white-space: nowrap;
    }
    .filter-table th {
        position: sticky;
        top: 2px;
        z-index: 1;
    }
    .filter-table input:not([type]), .filter-table input[type="text"] {
        margin: 0;
        box-sizing: border-box;
        height: 100%;
        width: 100%;
    }
    .filter-input-wrapper {
        position: absolute;
        top: 6px;
        right: 6px;
        bottom: 6px;
        left: 6px;
    }
    .filter-text-ellipsis {
        display: flex;
    }
    .filter-text-ellipsis > * {
        flex: 1;
        width: 1px;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .filter-button-group {
        margin: -.1em -.2em;
    }
    .filter-tags {
        margin: 2px -0.2em 0;
        text-align: left;
    }
    .filter-mask {
        margin: 1px;
        color: #81C7D4;
        background: #81C7D4;
    }
    .filter-mask-block {
        display: block;
        border: 1px solid #66BAB7;
        text-align: center !important;
    }
    .filter-input-wrapper {
      position: absolute;
      top: 6px;
      right: 6px;
      bottom: 6px;
      left: 6px;
    }
  `);

  // MENU
  const m = (() => {
    const list = [];

    const container = document.createElement("DIV");

    container.className = `td`;
    container.innerHTML = `<a class="mmdefault" href="javascript: void(0);" style="white-space: nowrap;">屏蔽</a>`;

    const content = container.querySelector("A");

    const create = (onclick) => {
      const anchor = document.querySelector("#mainmenu .td:last-child");

      anchor.before(container);

      content.onclick = onclick;
    };

    const update = () => {
      const count = list.length;

      if (count) {
        content.innerHTML = `屏蔽 <span class="small_colored_text_btn stxt block_txt_c0 vertmod">${count}</span>`;
      } else {
        content.innerHTML = `屏蔽`;
      }
    };

    const clear = () => {
      list.splice(0, list.length);

      update();
    };

    const add = ({ user, mode, subject, content, reason }) => {
      if (list.find((item) => item.content === content)) return;

      list.unshift({ user, mode, subject, content, reason });

      listModule.refresh();

      update();
    };

    return {
      create,
      clear,
      list,
      add,
    };
  })();

  // UI
  const u = (() => {
    const modules = {};

    const tabContainer = (() => {
      const c = document.createElement("div");

      c.className = "w100";
      c.innerHTML = `
          <div class="right_" style="margin-bottom: 5px;">
              <table class="stdbtn" cellspacing="0">
                  <tbody>
                      <tr></tr>
                  </tbody>
              </table>
          </div>
          <div class="clear"></div>
          `;

      return c;
    })();

    const tabPanelContainer = (() => {
      const c = document.createElement("div");

      c.style = "width: 80vw;";

      return c;
    })();

    const content = (() => {
      const c = document.createElement("div");

      c.append(tabContainer);
      c.append(tabPanelContainer);

      return c;
    })();

    const addModule = (() => {
      const tc = tabContainer.getElementsByTagName("tr")[0];
      const cc = tabPanelContainer;

      return (module) => {
        const tabBox = document.createElement("td");

        tabBox.innerHTML = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`;

        const tab = tabBox.childNodes[0];

        const toggle = () => {
          Object.values(modules).forEach((item) => {
            if (item.tab === tab) {
              item.tab.className = "nobr";
              item.content.style = "display: block";
              item.refresh();
            } else {
              item.tab.className = "nobr silver";
              item.content.style = "display: none";
            }
          });
        };

        tc.append(tabBox);
        cc.append(module.content);

        tab.onclick = toggle;

        modules[module.name] = {
          ...module,
          tab,
          toggle,
        };

        return modules[module.name];
      };
    })();

    return {
      content,
      modules,
      addModule,
    };
  })();

  // 屏蔽列表
  const listModule = (() => {
    const content = (() => {
      const c = document.createElement("div");

      c.style = "display: none";
      c.innerHTML = `
        <div class="filter-table-wrapper">
          <table class="filter-table forumbox">
            <thead>
              <tr class="block_txt_c0">
                <th class="c1" width="1">用户</th>
                <th class="c2" width="1">过滤方式</th>
                <th class="c3">内容</th>
                <th class="c4" width="1">原因</th>
              </tr>
            </thead>
            <tbody></tbody>
          </table>
        </div>
      `;

      return c;
    })();

    const refresh = (() => {
      const container = content.getElementsByTagName("tbody")[0];

      const func = () => {
        container.innerHTML = "";

        Object.values(m.list).forEach((item) => {
          const tc = document.createElement("tr");

          tc.className = `row${
            (container.querySelectorAll("TR").length % 2) + 1
          }`;

          tc.refresh = () => {
            const { user, mode, subject, content, reason } = item;

            tc.innerHTML = `
                <td class="c1">${user}</td>
                <td class="c2">${mode}</td>
                <td class="c3">
                  <div class="filter-text-ellipsis">
                    <span title="${content}">${subject || content}</span>
                  </div>
                </td>
                <td class="c4">${reason}</td>
              `;
          };

          tc.refresh();

          container.appendChild(tc);
        });
      };

      return func;
    })();

    return {
      name: "列表",
      content,
      refresh,
    };
  })();

  // 用户
  const userModule = (() => {
    const content = (() => {
      const c = document.createElement("div");

      c.style = "display: none";
      c.innerHTML = `
        <div class="filter-table-wrapper">
          <table class="filter-table forumbox">
            <thead>
              <tr class="block_txt_c0">
                <th class="c1" width="1">昵称</th>
                <th class="c2">标记</th>
                <th class="c3" width="1">过滤方式</th>
                <th class="c4" width="1">操作</th>
              </tr>
            </thead>
            <tbody></tbody>
          </table>
        </div>
      `;

      return c;
    })();

    const refresh = (() => {
      const container = content.getElementsByTagName("tbody")[0];

      const func = () => {
        container.innerHTML = "";

        Object.values(data.users).forEach((item) => {
          const tc = document.createElement("tr");

          tc.className = `row${
            (container.querySelectorAll("TR").length % 2) + 1
          }`;

          tc.refresh = () => {
            if (data.users[item.id]) {
              tc.innerHTML = `
                <td class="c1">
                    <a href="/nuke.php?func=ucp&uid=${
                      item.id
                    }" class="b nobr">[${
                item.name ? "@" + item.name : "#" + item.id
              }]</a>
                </td>
                <td class="c2">
                    ${item.tags
                      .map((tag) => {
                        if (data.tags[tag]) {
                          return `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`;
                        }
                      })
                      .join("")}
                </td>
                <td class="c3">
                    <div class="filter-table-button-group">
                      <button>${item.filterMode || FILTER_MODE[0]}</button>
                    </div>
                </td>
                <td class="c4">
                    <div class="filter-table-button-group">
                      <button>编辑</button>
                      <button>删除</button>
                    </div>
                </td>
              `;

              const actions = tc.getElementsByTagName("button");

              actions[0].onclick = () => {
                data.users[item.id].filterMode = switchFilterMode(
                  data.users[item.id].filterMode || FILTER_MODE[0]
                );

                actions[0].innerHTML = data.users[item.id].filterMode;

                saveData();
                reFilter();
              };

              actions[1].onclick = () => {
                editUser(item.id, item.name, tc.refresh);
              };

              actions[2].onclick = () => {
                if (confirm("是否确认?")) {
                  delete data.users[item.id];
                  container.removeChild(tc);

                  saveData();
                  reFilter();
                }
              };
            } else {
              tc.remove();
            }
          };

          tc.refresh();

          container.appendChild(tc);
        });
      };

      return func;
    })();

    return {
      name: "用户",
      content,
      refresh,
    };
  })();

  // 标记
  const tagModule = (() => {
    const content = (() => {
      const c = document.createElement("div");

      c.style = "display: none";
      c.innerHTML = `
        <div class="filter-table-wrapper">
          <table class="filter-table forumbox">
            <thead>
              <tr class="block_txt_c0">
                <th class="c1" width="1">标记</th>
                <th class="c2">列表</th>
                <th class="c3" width="1">过滤方式</th>
                <th class="c4" width="1">操作</th>
              </tr>
            </thead>
            <tbody></tbody>
          </table>
        </div>
      `;

      return c;
    })();

    const refresh = (() => {
      const container = content.getElementsByTagName("tbody")[0];

      const func = () => {
        container.innerHTML = "";

        Object.values(data.tags).forEach((item) => {
          const tc = document.createElement("tr");

          tc.className = `row${
            (container.querySelectorAll("TR").length % 2) + 1
          }`;

          tc.innerHTML = `
            <td class="c1">
                <b class="block_txt nobr" style="background:${
                  item.color
                }; color:#fff; margin: 0.1em 0.2em;">${item.name}</b>
            </td>
            <td class="c2">
                <button>${
                  Object.values(data.users).filter((user) =>
                    user.tags.find((tag) => tag === item.id)
                  ).length
                }
                </button>
                <div style="white-space: normal; display: none;">
                    ${Object.values(data.users)
                      .filter((user) =>
                        user.tags.find((tag) => tag === item.id)
                      )
                      .map(
                        (user) =>
                          `<a href="/nuke.php?func=ucp&uid=${
                            user.id
                          }" class="b nobr">[${
                            user.name ? "@" + user.name : "#" + user.id
                          }]</a>`
                      )
                      .join("")}
                </div>
            </td>
            <td class="c3">
                <div class="filter-table-button-group">
                  <button>${item.filterMode || FILTER_MODE[0]}</button>
                </div>
            </td>
            <td class="c4">
                <div class="filter-table-button-group">
                  <button>删除</button>
                </div>
            </td>
          `;

          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = (() => {
            let hide = true;
            return () => {
              hide = !hide;
              actions[0].nextElementSibling.style.display = hide
                ? "none"
                : "block";
            };
          })();

          actions[1].onclick = () => {
            data.tags[item.id].filterMode = switchFilterMode(
              data.tags[item.id].filterMode || FILTER_MODE[0]
            );

            actions[1].innerHTML = data.tags[item.id].filterMode;

            saveData();
            reFilter();
          };

          actions[2].onclick = () => {
            if (confirm("是否确认?")) {
              delete data.tags[item.id];

              Object.values(data.users).forEach((user) => {
                const index = user.tags.findIndex((tag) => tag === item.id);
                if (index >= 0) {
                  user.tags.splice(index, 1);
                }
              });

              container.removeChild(tc);

              saveData();
              reFilter();
            }
          };

          container.appendChild(tc);
        });
      };

      return func;
    })();

    return {
      name: "标记",
      content,
      refresh,
    };
  })();

  // 关键字
  const keywordModule = (() => {
    const content = (() => {
      const c = document.createElement("div");

      c.style = "display: none";
      c.innerHTML = `
        <div class="filter-table-wrapper">
          <table class="filter-table forumbox">
            <thead>
              <tr class="block_txt_c0">
                <th class="c1">列表</th>
                <th class="c2" width="1">过滤方式</th>
                <th class="c3" width="1">包括内容</th>
                <th class="c4" width="1">操作</th>
              </tr>
            </thead>
            <tbody></tbody>
          </table>
        </div>
        <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用&quot;|&quot;隔开,&quot;ABC|DEF&quot;即为屏蔽带有ABC或者DEF的内容。</div>
      `;

      return c;
    })();

    const refresh = (() => {
      const container = content.getElementsByTagName("tbody")[0];

      const func = () => {
        container.innerHTML = "";

        Object.values(data.keywords).forEach((item) => {
          const tc = document.createElement("tr");

          tc.className = `row${
            (container.querySelectorAll("TR").length % 2) + 1
          }`;

          tc.innerHTML = `
            <td class="c1">
                <div class="filter-input-wrapper">
                  <input value="${item.keyword || ""}" />
                </div>
            </td>
            <td class="c2">
                <div class="filter-table-button-group">
                  <button>${item.filterMode || FILTER_MODE[0]}</button>
                </div>
            </td>
            <td class="c3">
              <div style="text-align: center;">
                <input type="checkbox" ${
                  item.filterLevel ? `checked="checked"` : ""
                } />
              </div>
            </td>
            <td class="c4">
                <div class="filter-table-button-group">
                    <button>保存</button>
                    <button>删除</button>
                </div>
            </td>
          `;

          const inputElement = tc.querySelector("INPUT");
          const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
          };

          actions[1].onclick = () => {
            if (inputElement.value) {
              data.keywords[item.id] = {
                id: item.id,
                keyword: inputElement.value,
                filterMode: actions[0].innerHTML,
                filterLevel: levelElement.checked ? 1 : 0,
              };

              saveData();
              refresh();
            }
          };

          actions[2].onclick = () => {
            if (confirm("是否确认?")) {
              delete data.keywords[item.id];

              saveData();
              refresh();
            }
          };

          container.appendChild(tc);
        });

        {
          const tc = document.createElement("tr");

          tc.className = `row${
            (container.querySelectorAll("TR").length % 2) + 1
          }`;

          tc.innerHTML = `
            <td class="c1">
                <div class="filter-input-wrapper">
                  <input value="" />
                </div>
            </td>
            <td class="c2">
                <div class="filter-table-button-group">
                  <button>${FILTER_MODE[0]}</button>
                </div>
            </td>
            <td class="c3">
              <div style="text-align: center;">
                <input type="checkbox" />
              </div>
            </td>
            <td class="c4">
                <div class="filter-table-button-group">
                  <button>添加</button>
                </div>
            </td>
          `;

          const inputElement = tc.querySelector("INPUT");
          const levelElement = tc.querySelector(`INPUT[type="checkbox"]`);
          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
          };

          actions[1].onclick = () => {
            if (inputElement.value) {
              addKeyword(
                inputElement.value,
                actions[0].innerHTML,
                levelElement.checked ? 1 : 0
              );

              saveData();
              refresh();
            }
          };

          container.appendChild(tc);
        }
      };

      return func;
    })();

    return {
      name: "关键字",
      content,
      refresh,
    };
  })();

  // 属地
  const locationModule = (() => {
    const content = (() => {
      const c = document.createElement("div");

      c.style = "display: none";
      c.innerHTML = `
        <div class="filter-table-wrapper">
          <table class="filter-table forumbox">
            <thead>
              <tr class="block_txt_c0">
                <th class="c1">列表</th>
                <th class="c2" width="1">过滤方式</th>
                <th class="c3" width="1">操作</th>
              </tr>
            </thead>
            <tbody></tbody>
          </table>
        </div>
        <div class="silver" style="margin-top: 10px;">支持正则表达式。比如同类型的可以写在一条规则内用&quot;|&quot;隔开,&quot;ABC|DEF&quot;即为屏蔽带有ABC或者DEF的内容。<br/>属地过滤功能需要占用额外的资源,请谨慎开启</div>
      `;

      return c;
    })();

    const refresh = (() => {
      const container = content.getElementsByTagName("tbody")[0];

      const func = () => {
        container.innerHTML = "";

        Object.values(data.locations).forEach((item) => {
          const tc = document.createElement("tr");

          tc.className = `row${
            (container.querySelectorAll("TR").length % 2) + 1
          }`;

          tc.innerHTML = `
            <td class="c1">
                <div class="filter-input-wrapper">
                  <input value="${item.keyword || ""}" />
                </div>
            </td>
            <td class="c2">
                <div class="filter-table-button-group">
                  <button>${item.filterMode || FILTER_MODE[0]}</button>
                </div>
            </td>
            <td class="c3">
                <div class="filter-table-button-group">
                    <button>保存</button>
                    <button>删除</button>
                </div>
            </td>
          `;

          const inputElement = tc.querySelector("INPUT");
          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
          };

          actions[1].onclick = () => {
            if (inputElement.value) {
              data.locations[item.id] = {
                id: item.id,
                keyword: inputElement.value,
                filterMode: actions[0].innerHTML,
              };

              saveData();
              refresh();
            }
          };

          actions[2].onclick = () => {
            if (confirm("是否确认?")) {
              delete data.locations[item.id];

              saveData();
              refresh();
            }
          };

          container.appendChild(tc);
        });

        {
          const tc = document.createElement("tr");

          tc.className = `row${
            (container.querySelectorAll("TR").length % 2) + 1
          }`;

          tc.innerHTML = `
            <td class="c1">
                <div class="filter-input-wrapper">
                  <input value="" />
                </div>
            </td>
            <td class="c2">
                <div class="filter-table-button-group">
                  <button>${FILTER_MODE[0]}</button>
                </div>
            </td>
            <td class="c3">
                <div class="filter-table-button-group">
                  <button>添加</button>
                </div>
            </td>
          `;

          const inputElement = tc.querySelector("INPUT");
          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            actions[0].innerHTML = switchFilterMode(actions[0].innerHTML);
          };

          actions[1].onclick = () => {
            if (inputElement.value) {
              addLocation(inputElement.value, actions[0].innerHTML);

              saveData();
              refresh();
            }
          };

          container.appendChild(tc);
        }
      };

      return func;
    })();

    return {
      name: "属地",
      content,
      refresh,
    };
  })();

  // 猎巫
  const witchHuntModule = (() => {
    const content = (() => {
      const c = document.createElement("div");

      c.style = "display: none";
      c.innerHTML = `
        <div class="filter-table-wrapper">
          <table class="filter-table forumbox">
            <thead>
              <tr class="block_txt_c0">
                <th class="c1">版面</th>
                <th class="c2">标签</th>
                <th class="c3" width="1">操作</th>
              </tr>
            </thead>
            <tbody></tbody>
          </table>
        </div>
        <div class="silver" style="margin-top: 10px;">猎巫模块需要占用额外的资源,请谨慎开启<br/>该功能为实验性功能,仅判断用户是否曾经在某个版面发言<br/>未来可能会加入发言的筛选或是屏蔽功能,也可能移除此功能</div>
      `;

      return c;
    })();

    const refresh = (() => {
      const container = content.getElementsByTagName("tbody")[0];

      const func = () => {
        container.innerHTML = "";

        Object.values(witchHunter.data).forEach((item, index) => {
          const tc = document.createElement("tr");

          tc.className = `row${
            (container.querySelectorAll("TR").length % 2) + 1
          }`;

          tc.innerHTML = `
            <td class="c1">
                <div class="filter-input-wrapper">
                  <a href="/thread.php?fid=${item.fid}" class="b nobr">[${item.name}]</a>
                </div>
            </td>
            <td class="c2">
                <b class="block_txt nobr" style="background:${item.color}; color:#fff; margin: 0.1em 0.2em;">${item.label}</b>
            </td>
            <td class="c3">
                <div class="filter-table-button-group">
                    <button>删除</button>
                </div>
            </td>
          `;

          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            if (confirm("是否确认?")) {
              witchHunter.remove(item.id);

              refresh();
            }
          };

          container.appendChild(tc);
        });

        {
          const tc = document.createElement("tr");

          tc.className = `row${
            (container.querySelectorAll("TR").length % 2) + 1
          }`;

          tc.innerHTML = `
            <td class="c1">
                <div class="filter-input-wrapper">
                  <input value="" placeholder="版面ID" />
                </div>
            </td>
            <td class="c2">
                <div class="filter-input-wrapper">
                  <input value="" />
                </div>
            </td>
            <td class="c3">
                <div class="filter-table-button-group">
                  <button>添加</button>
                </div>
            </td>
          `;

          const inputElement = tc.getElementsByTagName("INPUT");
          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = async () => {
            const fid = parseInt(inputElement[0].value, 10);
            const tag = inputElement[1].value.trim();

            if (isNaN(fid) || tag.length === 0) {
              return;
            }

            await witchHunter.add(fid, tag);

            refresh();
          };

          container.appendChild(tc);
        }
      };

      return func;
    })();

    return {
      name: "猎巫",
      content,
      refresh,
    };
  })();

  // 通用设置
  const commonModule = (() => {
    const content = (() => {
      const c = document.createElement("div");

      c.style = "display: none";

      return c;
    })();

    const refresh = (() => {
      const container = content;

      const func = () => {
        container.innerHTML = "";

        // 默认过滤方式
        {
          const tc = document.createElement("div");

          tc.innerHTML += `
            <div>默认过滤方式</div>
            <div></div>
            <div class="silver" style="margin-top: 10px;">${FILTER_TIPS}</div>
          `;

          ["标记", "遮罩", "隐藏"].forEach((item, index) => {
            const ele = document.createElement("SPAN");

            ele.innerHTML += `
            <input id="s-fm-${index}" type="radio" name="filterType" ${
              data.options.filterMode === item && "checked"
            }>
            <label for="s-fm-${index}" style="cursor: pointer;">${item}</label>
            `;

            const inp = ele.querySelector("input");

            inp.onchange = () => {
              if (inp.checked) {
                data.options.filterMode = item;
                saveData();
                reFilter();
              }
            };

            tc.querySelectorAll("div")[1].append(ele);
          });

          container.appendChild(tc);
        }

        // 小号过滤(时间)
        {
          const tc = document.createElement("div");

          tc.innerHTML += `
              <br/>
              <div>
                隐藏注册时间小于<input value="${
                  (data.options.filterRegdateLimit || 0) / 86400000
                }" maxLength="4" style="width: 48px;" />天的用户
                <button>确认</button>
              </div>
            `;

          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            const v = actions[0].previousElementSibling.value;

            const n = Number(v) || 0;

            data.options.filterRegdateLimit = n < 0 ? 0 : n * 86400000;

            saveData();
            reFilter();
          };

          container.appendChild(tc);
        }

        // 小号过滤(发帖数)
        {
          const tc = document.createElement("div");

          tc.innerHTML += `
              <br/>
              <div>
                隐藏发帖数量小于<input value="${
                  data.options.filterPostnumLimit || 0
                }" maxLength="5" style="width: 48px;" />贴的用户
                <button>确认</button>
              </div>
            `;

          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            const v = actions[0].previousElementSibling.value;

            const n = Number(v) || 0;

            data.options.filterPostnumLimit = n < 0 ? 0 : n;

            saveData();
            reFilter();
          };

          container.appendChild(tc);
        }

        // 流量号过滤(主题比例)
        {
          const tc = document.createElement("div");

          tc.innerHTML += `
              <br/>
              <div>
                隐藏发帖比例大于<input value="${
                  data.options.filterTopicRateLimit || 100
                }" maxLength="3" style="width: 48px;" />%的用户
                <button>确认</button>
              </div>
            `;

          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            const v = actions[0].previousElementSibling.value;

            const n = Number(v) || 100;

            if (n <= 0 || n > 100) {
              return;
            }

            data.options.filterTopicRateLimit = n;

            saveData();
            reFilter();
          };

          container.appendChild(tc);
        }

        // 声望过滤
        {
          const tc = document.createElement("div");

          tc.innerHTML += `
              <br/>
              <div>
                隐藏版面声望低于<input value="${
                  data.options.filterReputationLimit || ""
                }" maxLength="5" style="width: 48px;" />点的用户
                <button>确认</button>
              </div>
            `;

          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            const v = actions[0].previousElementSibling.value;

            const n = Number(v);

            data.options.filterReputationLimit = n;

            saveData();
            reFilter();
          };

          container.appendChild(tc);
        }

        // 匿名过滤
        {
          const tc = document.createElement("div");

          tc.innerHTML += `
              <br/>
              <div>
                <label>
                  隐藏匿名的用户
                  <input type="checkbox" ${
                    data.options.filterAnony ? `checked="checked"` : ""
                  } />
                </label>
              </div>
            `;

          const checkbox = tc.querySelector("input");

          checkbox.onchange = () => {
            const v = checkbox.checked;

            data.options.filterAnony = v;

            saveData();
            reFilter();
          };

          container.appendChild(tc);
        }

        // 删除没有标记的用户
        {
          const tc = document.createElement("div");

          tc.innerHTML += `
            <br/>
            <div>
                <button>删除没有标记的用户</button>
            </div>
          `;

          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            if (confirm("是否确认?")) {
              Object.values(data.users).forEach((item) => {
                if (item.tags.length === 0) {
                  delete data.users[item.id];
                }
              });

              saveData();
              reFilter();
            }
          };

          container.appendChild(tc);
        }

        // 删除没有用户的标记
        {
          const tc = document.createElement("div");

          tc.innerHTML += `
            <br/>
            <div>
                <button>删除没有用户的标记</button>
            </div>
          `;

          const actions = tc.getElementsByTagName("button");

          actions[0].onclick = () => {
            if (confirm("是否确认?")) {
              Object.values(data.tags).forEach((item) => {
                if (
                  Object.values(data.users).filter((user) =>
                    user.tags.find((tag) => tag === item.id)
                  ).length === 0
                ) {
                  delete data.tags[item.id];
                }
              });

              saveData();
              reFilter();
            }
          };

          container.appendChild(tc);
        }

        // 删除非激活中的用户
        {
          const tc = document.createElement("div");

          tc.innerHTML += `
            <br/>
            <div>
                <button>删除非激活中的用户</button>
                <div style="white-space: normal;"></div>
            </div>
          `;

          const action = tc.querySelector("button");
          const list = action.nextElementSibling;

          action.onclick = () => {
            if (confirm("是否确认?")) {
              const waitingQueue = Object.values(data.users).map(
                (item) => () =>
                  new Promise((resolve) => {
                    fetch(
                      `/nuke.php?lite=js&__lib=ucp&__act=get&uid=${item.id}`
                    )
                      .then((res) => res.blob())
                      .then((blob) => {
                        const reader = new FileReader();

                        reader.onload = () => {
                          const text = reader.result;
                          const result = JSON.parse(
                            text.replace(
                              "window.script_muti_get_var_store=",
                              ""
                            )
                          );

                          if (!result.error) {
                            const { bit } = result.data[0];

                            const activeInfo = n.activeInfo(0, 0, bit);

                            const activeType = activeInfo[1];

                            if (!["ACTIVED", "LINKED"].includes(activeType)) {
                              list.innerHTML += `<a href="/nuke.php?func=ucp&uid=${
                                item.id
                              }" class="b nobr">[${
                                item.name ? "@" + item.name : "#" + item.id
                              }]</a>`;

                              delete data.users[item.id];
                            }
                          }

                          resolve();
                        };

                        reader.readAsText(blob, "GBK");
                      })
                      .catch(() => {
                        resolve();
                      });
                  })
              );

              const queueLength = waitingQueue.length;

              const execute = () => {
                if (waitingQueue.length) {
                  const next = waitingQueue.shift();

                  action.innerHTML = `删除非激活中的用户 (${
                    queueLength - waitingQueue.length
                  }/${queueLength})`;
                  action.disabled = true;

                  next().finally(execute);
                } else {
                  action.disabled = false;

                  saveData();
                  reFilter();
                }
              };

              execute();
            }
          };

          container.appendChild(tc);
        }
      };

      return func;
    })();

    return {
      name: "通用设置",
      content,
      refresh,
    };
  })();

  u.addModule(listModule).toggle();
  u.addModule(userModule);
  u.addModule(tagModule);
  u.addModule(keywordModule);
  u.addModule(locationModule);
  u.addModule(witchHuntModule);
  u.addModule(commonModule);

  // 增加菜单项
  (() => {
    let window;

    m.create(() => {
      if (window === undefined) {
        window = n.createCommmonWindow();
      }

      window._.addContent(null);
      window._.addTitle(`屏蔽`);
      window._.addContent(u.content);
      window._.show();
    });
  })();

  // 执行过滤
  (() => {
    const hookFunction = (object, functionName, callback) => {
      ((originalFunction) => {
        object[functionName] = function () {
          const returnValue = originalFunction.apply(this, arguments);

          callback.apply(this, [returnValue, originalFunction, arguments]);

          return returnValue;
        };
      })(object[functionName]);
    };

    const initialized = {
      topicArg: false,
      postArg: false,
    };

    hookFunction(n, "eval", () => {
      if (Object.values(initialized).findIndex((item) => item === false) < 0) {
        return;
      }

      if (n.topicArg && initialized.topicArg === false) {
        hookFunction(n.topicArg, "add", reFilter);

        initialized.topicArg = true;
      }

      if (n.postArg && initialized.postArg === false) {
        hookFunction(n.postArg, "proc", reFilter);

        initialized.postArg = true;
      }
    });

    reFilter();
  })();
})(commonui, __CURRENT_UID);