Greasy Fork

NGA Filter

troll must die

目前为 2021-06-18 提交的版本。查看 最新版本

// ==UserScript==
// @name        NGA Filter
// @namespace   https://greasyfork.org/users/263018
// @version     1.3.2
// @author      snyssss
// @description troll must die

// @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: {
        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.substr(2, 2)}` / 255,
      `0x${hex.substr(2, 2)}` / 255 / 2 + 0.25,
      `0x${hex.substr(4, 2)}` / 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 = (uid, content, level) => {
    let result = -1;

    const user = data.users[uid];

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

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

    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 r = keywords
          .filter((item) => item.keyword && item.filterMode !== "显示")
          .filter((item) => (item.filterLevel || 0) >= level)
          .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 result;
      })();

      if (filterMode > 0) {
        return filterMode;
      }

      result = filterMode;
    }

    return result;
  };

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

    quotes.forEach((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;
      })();

      if (uid) {
        const filterMode = (() => {
          const mode = getFilterMode(uid, quote.innerText, 1);

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

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

          return "";
        })();

        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 = () => {
    const tPage = location.pathname === "/thread.php";
    const pPage = location.pathname === "/read.php";
    const uPage = location.pathname === "/nuke.php";

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

      Object.values(tData).forEach((item) => {
        if (item.containerC) return;

        const uid =
          item[2].search.match(/uid=(\S+)/) &&
          item[2].search.match(/uid=(\S+)/)[1];

        const filterMode = (() => {
          const mode = getFilterMode(uid, item[1].innerText, 0);

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

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

          return "";
        })();

        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;

      Object.values(pData).forEach((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 ||
          (() => {
            const uid = item.pAid;

            const filterMode = (() => {
              const mode = getFilterMode(uid, item.contentC.innerText, 1);

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

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

              return "";
            })();

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

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

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