Greasy Fork

Greasy Fork is available in English.

NGA Filter

troll must die

当前为 2023-03-31 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴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   http://greasyfork.icu/users/263018
// @version     1.9.5
// @author      snyssss
// @description 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,
        filterReputationLimit: NaN,
        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 getFilterModeByUserInfo = async (userInfo, reputation) => {
    const filterRegdateLimit = data.options.filterRegdateLimit || 0;

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

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

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

      if (
        filterRegdateLimit > 0 &&
        regdate * 1000 > new Date() - filterRegdateLimit
      ) {
        return true;
      }

      if (filterPostnumLimit > 0 && postnum < filterPostnumLimit) {
        return true;
      }
    }

    if (Number.isNaN(filterReputationLimit) === false) {
      if (reputation < filterReputationLimit) {
        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 && 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)) {
        return FILTER_MODE.indexOf("隐藏");
      }
    }

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

      if (filterMode > 0) {
        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) {
        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) {
              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) {
              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) {
            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)) {
                  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) => {
            if (~~item.pAid === 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"]');

                  ele.onclick = null;

                  return ele;
                })();

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

                  tc.className = "filter-tags";

                  item.uInfoC.appendChild(tc);

                  return tc;
                })();
            }

            item.pName =
              item.pName ||
              item.uInfoC.getElementsByClassName("author")[0].innerText;

            item.reFilter =
              item.reFilter ||
              (async () => {
                const uid = item.pAid;

                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 (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;
    }
  `);

  // 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 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.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>
                <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(userModule).toggle();
  u.addModule(tagModule);
  u.addModule(keywordModule);
  u.addModule(locationModule);
  u.addModule(witchHuntModule);
  u.addModule(commonModule);

  // 增加菜单项
  (() => {
    const title = "过滤设置";

    let window;

    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 anchor = document.querySelector("#mainmenu .td:last-child");

    anchor.before(container);

    content.onclick = () => {
      if (window === undefined) {
        window = n.createCommmonWindow();
      }

      window._.addContent(null);
      window._.addTitle(title);
      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);