NGA Filter

troll must die

נכון ליום 25-02-2022. ראה הגרסה האחרונה.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        NGA Filter
// @namespace   https://greasyfork.org/users/263018
// @version     1.5.0
// @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

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

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

  const key = "NGAFilter";

  // 过滤提示
  const FILTER_TIPS =
    "过滤顺序:用户 &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: {},
      options: {
        filterRegdateLimit: 0,
        filterPostnumLimit: 0,
        filterMode: "隐藏",
      },
    };

    const v = GM_getValue(key);

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

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

  // 保存数据
  const saveData = () => {
    GM_setValue(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 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 getFilterMode = async (uid, subject, content) => {
    let result = -1;

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

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

    const user = data.users[uid];

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

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

    if (filterRegdateLimit > 0 || filterPostnumLimit > 0) {
      if (uid && uid > 0) {
        const userInfo = n.userInfo.users[uid];
        
        if (userInfo) {
          if (
            (userInfo.regdate * 1000 > new Date() - filterRegdateLimit) ||
            (userInfo.postnum && userInfo.postnum < filterPostnumLimit) ||
            (userInfo.posts && userInfo.posts < filterPostnumLimit)
          ) {
            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;
    }

    return result;
  };
  
  // 根据 TID 获取过滤方式
  const getFilterModeByTID = async (tid) => {
    const tInfo = await new Promise((resolve) => {
      fetch(`/read.php?tid=${tid}&lite=js&__lib=ucp&__act=get`)
        .then((res) => res.blob())
        .then((blob) => {
          const reader = new FileReader();

          reader.onload = () => {
            const text = reader.result;
            
            // 修复接口数据问题
            const fixedText = text.replace(/"alterinfo"[^,]+,/g, "");
            
            const result = JSON.parse(
              fixedText.replace("window.script_muti_get_var_store=", "")
            );

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

    Object.values(tInfo["__U"]).map((item) => {
      const uid = item["uid"];

      if (n.userInfo.users[uid] === undefined) {
        n.userInfo.users[uid] = item;
      }
    });
    
    const uid = tInfo["__T"]["authorid"];
    
    const subject = tInfo["__R"]["0"]["subject"];
    
    const content = tInfo["__R"]["0"]["content"];
    
    return getFilterMode(uid, subject, content);
  };

  // 处理引用
  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";
      const uPage = location.pathname === "/nuke.php";

      if (tPage && new RegExp(`authorid=${self}`).test(location.search)) {
        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 getFilterModeByTID(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("");
                }
              });

            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();
          })
        );
      } else if (uPage) {
        const container = document.getElementById("ucp_block");

        if (container.firstChild) {
          const uid = container.innerText.match(/用户ID\s*:\s*(\S+)/)[1];

          const name = container.innerText.match(/用户名\s*:\s*(\S+)/)[1];

          container.tagC =
            container.tagC ||
            (() => {
              const c = document.createElement("span");

              c.innerHTML = `
                    <h2 class="catetitle">:: ${name} 的标记 ::</h2>
                    <div class="cateblock" style="text-align: left; line-height: 1.8em;">
                        <div class="contentBlock" style="padding: 5px 10px;">
                            <span>
                                <ul class="actions" style="padding: 0px; margin: 0px;">
                                    <li style="padding-right: 5px;">
                                        <span>
                                            <a href="javascript: void(0);">[编辑 ${name} 的标记]</a>
                                        </span>
                                    </li>
                                    <div class="clear"></div>
                                </ul>
                            </span>
                            <div class="filter-tags"></div>
                            <div class="clear"></div>
                        </div>
                    </div>
                `;

              c.getElementsByTagName("a")[0].onclick = () => {
                editUser(uid, name, container.refresh);
              };

              container.firstChild.insertBefore(
                c,
                container.firstChild.childNodes[1]
              );

              return c.getElementsByClassName("filter-tags")[0];
            })();

          container.refresh = () => {
            container.tagC.innerHTML = data.users[uid].tags
              .map(
                (tag) =>
                  `<b class="block_txt nobr" style="background:${data.tags[tag].color}; color:#fff; margin: 0.1em 0.2em;">${data.tags[tag].name}</b>`
              )
              .join("");
          };

          container.refresh();
        }
      }
    };

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

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

    let window;

    n.mainMenu.addItemOnTheFly(title, null, () => {
      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;
      }
    });

    if (n.ucp) {
      hookFunction(n.ucp, "_echo", reFilter);
    }

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