NGA Filter

troll must die

La data de 03-02-2020. Vezi ultima versiune.

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://greatest.deepsurf.us/users/263018
// @version     1.0.4
// @author      snyssss
// @description troll must die

// @match       *bbs.nga.cn/thread.php?fid=*
// @match       *bbs.nga.cn/read.php?tid=*
// @match       *bbs.nga.cn/nuke.php?*
// @match       *ngabbs.com/thread.php?fid=*
// @match       *ngabbs.com/read.php?tid=*
// @match       *ngabbs.com/nuke.php?*

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

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

((n, self) => {
  "use strict";

  if (n === undefined) return;

  const key = "NGAFilter";

  // 数据
  const data = (() => {
    const d = {
      tags: {},
      users: {},
      options: {
        filterMode: 0,
        keyword: ""
      }
    };
    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,
      enabled: true
    };

    saveData();

    return id;
  };

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

    data.users[id] = {
      id,
      name,
      tags,
      enabled: isEnabled
    };

    saveData();

    return data.users[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,
          trollMap[item].map(tag => addTag(tag))
        );
      });

      data.options.filterMode = filterMode ? 0 : 1;
      data.options.keyword = filterKeyword;

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

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

      content.className = "w100";
      content.innerHTML = `
        <table class="filter-table" style="min-width: 400px;">
            <tbody>
                ${Object.values(data.tags)
                  .map(
                    tag => `
                        <tr>
                            <td>
                                <b class="block_txt nobr" style="background:${
                                  tag.color
                                }; color:#fff; margin: 0.1em 0.2em;">${
                      tag.name
                    }</b>
                            </td>
                            <td>
                                <input type="checkbox" value="${
                                  tag.id
                                }" ${user &&
                      user.tags.find(item => item === tag.id) &&
                      "checked"}/>
                            </td>
                        </tr>
                    `
                  )
                  .join("")}
            </tbody>
            <tfoot>
                <tr>
                <td colspan="2">
                    <input placeholder="一次性添加多个标记用&quot;|&quot;隔开,不会添加重名标记" style="width: -webkit-fill-available;" />
                </td>
                </tr>
            </tfoot>
        </table>
        <div style="margin: 10px 0;">
            <button>${user && user.enabled === false ? "启用" : "禁用"}</button>
            <div class="right_">
                <button>删除</button>
                <button>保存</button>
            </div>
        </div>
    `;

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

      actions[0].onclick = () => {
        actions[0].innerText =
          actions[0].innerText === "禁用" ? "启用" : "禁用";
      };

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

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

          Object.values(tData).forEach(item => {
            const uid =
              item[2].search.match(/uid=(\S+)/) &&
              item[2].search.match(/uid=(\S+)/)[1];

            const user = data.users[uid];

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

            const isBlock =
              (user &&
                user.enabled &&
                (tags.length === 0 ||
                  tags.filter(tag => tag.enabled).length)) ||
              (data.options.keyword.length &&
                item[1].innerText.search(data.options.keyword) >= 0);

            item.contentC = item[1];

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

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

            item.containerC.style =
              isBlock && data.options.filterMode === 0 ? "display: none;" : "";

            item.contentC.style =
              isBlock && data.options.filterMode === 1
                ? "text-decoration: line-through;"
                : "";
          });
        };
      } else if (pPage) {
        return () => {
          const pData = n.postArg.data;

          Object.values(pData).forEach(item => {
            if (~~item.pAid === self) return;

            if (typeof item.i === "number") {
              item.actionC =
                item.actionC ||
                (() => {
                  const ele = item.uInfoC.firstElementChild.lastElementChild;

                  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 user = data.users[item.pAid];

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

                const isBlock =
                  user &&
                  user.enabled &&
                  (tags.length === 0 || tags.filter(tag => tag.enabled).length);

                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;

                    while (
                      temp.className !== "forumbox postbox" &&
                      temp.className !== "comment_c left"
                    ) {
                      temp = temp.parentNode;
                    }

                    return temp;
                  })();

                item.containerC.style.display =
                  isBlock && data.options.filterMode === 0 ? "none" : "";

                item.contentC.innerHTML =
                  isBlock && data.options.filterMode === 1
                    ? `
                    <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_${user.id}')].forEach(item => item.style.display = '')">点击查看</a>
                        <div style="display: none;" name="troll_${user.id}">
                            ${item.contentB}
                        </div>
                    </div>`
                    : item.contentB;

                item.avatarC.style.display = isBlock ? "none" : "";

                if (item.actionC) {
                  item.actionC.style =
                    user && user.enabled
                      ? "background: #cb4042;"
                      : "background: #aaa;";
                }

                if (item.tagC) {
                  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) {
                    editUser(item.pAid, item.pName, item.reFilter);
                  } else {
                    if (user) {
                      if (user.tags.length) {
                        user.enabled = !user.enabled;
                        user.name = item.pName;
                      } else {
                        delete data.users[user.id];
                      }
                    } else {
                      addUser(item.pAid, item.pName);
                    }

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

            item.reFilter();
          });
        };
      } else if (uPage) {
        return () => {
          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();
          }
        };
      }

      return () => {};
    })();

    const observer = new MutationObserver(mutations => {
      if (mutations.find(mutation => mutation.addedNodes.length)) {
        func();
      }
    });

    if (tPage) {
      observer.observe(document.getElementById("topicrows"), {
        childList: true
      });
    } else if (pPage) {
      observer.observe(document.getElementById("m_posts_c"), {
        childList: true
      });
    } else if (uPage) {
      observer.observe(document.getElementById("ucp_block"), {
        childList: true
      });
    }

    func();

    return func;
  })();

  // STYLE
  GM_addStyle(`
    .filter-tags {
        margin: 2px -0.2em 0;
        text-align: left;
    }
    .filter-table {
        border: 1px solid #ead5bc;
        border-left: none;
        border-bottom: none;
        width: 99.95%;
    }
    .filter-table thead {
        background-color: #591804;
        color: #fff8e7;
    }
    .filter-table tbody tr {
        background-color: #fff0cd;
    }
    .filter-table tbody tr:nth-of-type(odd) {
        background-color: #fff8e7;
    }
    .filter-table td {
        border: 1px solid #ead5bc;
        border-top: none;
        border-right: none;
        padding: 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 =
        "min-width: 20vw; max-width: 80vw; max-height: 80vh; overflow: auto;";
      c.innerHTML = `
            `;

      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 blockModule = (() => {
    const content = (() => {
      const c = document.createElement("div");

      c.style = "display: none";
      c.innerHTML = `
        <table class="filter-table">
            <thead>
                <tr>
                    <td>
                        <b style="margin: 0.1em 0.2em;">昵称</b>
                    </td>
                    <td>
                        <b style="margin: 0.1em 0.2em;">标记</b>
                    </td>
                    <td>
                        <b style="margin: 0.1em 0.2em;">操作</b>
                    </td>
                </tr>
            </thead>
            <tbody></tbody>
        </table>
            `;

      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.refresh = () => {
            if (data.users[item.id]) {
              tc.innerHTML = `
                <tr>
                    <td>
                        <a href="/nuke.php?func=ucp&uid=${
                          item.id
                        }" class="b nobr">[${
                item.name ? "@" + item.name : "#" + item.id
              }]</a>
                    </td>
                    <td>
                        ${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="nobr">
                        <button>编辑</button>
                        <button>${item.enabled ? "禁用" : "启用"}</button>
                        <button>删除</button>
                    </td>
                </tr>
                `;

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

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

              actions[1].onclick = () => {
                data.users[item.id].enabled = !data.users[item.id].enabled;
                actions[1].innerHTML = data.users[item.id].enabled
                  ? "禁用"
                  : "启用";

                saveData();
                reFilter();
              };

              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 = `
        <table class="filter-table">
            <thead>
                <tr>
                    <td>
                        <b style="margin: 0.1em 0.2em;">标记</b>
                    </td>
                    <td>
                        <b style="margin: 0.1em 0.2em;">列表</b>
                    </td>
                    <td>
                        <b style="margin: 0.1em 0.2em;">操作</b>
                    </td>
                </tr>
            </thead>
            <tbody></tbody>
        </table>
            `;

      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.innerHTML = `
            <tr>
                <td>
                    <b class="block_txt nobr" style="background:${
                      item.color
                    }; color:#fff; margin: 0.1em 0.2em;">${item.name}</b>
                </td>
                <td>
                    <button>${
                      Object.values(data.users).filter(user =>
                        user.tags.find(tag => tag === item.id)
                      ).length
                    }
                    </button>
                    <div style="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="nobr">
                    <button>${item.enabled ? "禁用" : "启用"}</button>
                    <button>删除</button>
                </td>
            </tr>
            `;

          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].enabled = !data.tags[item.id].enabled;
            actions[1].innerHTML = data.tags[item.id].enabled ? "禁用" : "启用";

            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 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>
                <input value="${data.options.keyword}"/>
                <button>确认</button>
            </div>
          `;

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

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

            data.options.keyword = v;

            saveData();
            reFilter();
          };

          container.appendChild(tc);
        }

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

          tc.innerHTML += `
            <br/>
            <div>过滤方式</div>
            <div>
                <input type="radio" name="filterType" ${data.options
                  .filterMode === 0 && "checked"}>
                <span>隐藏</span>
                <input type="radio" name="filterType" ${data.options
                  .filterMode === 1 && "checked"}>
                <span>标记</span>
                <button>确认</button>
            </div>
          `;

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

          actions[0].onclick = () => {
            const values = document.getElementsByName("filterType");

            for (let i = 0, length = values.length; i < length; i++) {
              if (values[i].checked) {
                data.options.filterMode = i;
                break;
              }
            }

            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(blockModule).toggle();
  u.addModule(tagModule);
  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();
    });
  })();
})(commonui, __CURRENT_UID);