Greasy Fork is available in English.

NGA Filter

troll must die

От 04.11.2022. Виж последната версия.

// ==UserScript==
// @name        NGA Filter
// @namespace   https://greasyfork.org/users/263018
// @version     1.9.3
// @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 ele = item.uInfoC.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);
        }
      };

      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);