抒发森林增强

抒发森林增强,只看洞主,下载图片

// ==UserScript==
// @name         抒发森林增强
// @namespace    https://github.com/rktccn/treehollowEnhance
// @supportURL   https://github.com/rktccn/treehollowEnhance
// @homepageURL  https://github.com/rktccn/treehollowEnhance
// @version      0.3.1
// @description  抒发森林增强,只看洞主,下载图片
// @author       RoIce
// @match        *://web.treehollow.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=greasespot.net
// @grant        GM_addStyle
// @grant        GM_notification
// @run-at       document-start
// @license      MIT
// ==/UserScript==

// 2022-4-28 更新屏蔽功能和菜单开关功能
// 修复回到顶部的问题,应该修复了

(function () {
  "use strict";
  // Your code here...

  let css = `.active{
    background-color: rgb(243, 142, 4) !important;
  }`;
  GM_addStyle(css);

  // 原始数据
  let originalreplyList = [];

  // 通知信息
  let noticeLength = 0; // 总通知数
  let newNoticeList = []; // 新通知
  let noticeList = []; // 临时储存通知

  // 回复内容节点
  let replyNodes = [];

  // 检测屏幕大小,是否为移动端
  const isMobile = () => {
    return window.screen.width < 768;
  };

  // 拦截获取请求
  function addXMLRequestCallback(callback) {
    var oldSend, i;
    if (XMLHttpRequest.callbacks) {
      // we've already overridden send() so just add the callback
      XMLHttpRequest.callbacks.push(callback);
    } else {
      // create a callback queue
      XMLHttpRequest.callbacks = [callback];
      // store the native send()
      oldSend = XMLHttpRequest.prototype.send;
      // override the native send()
      XMLHttpRequest.prototype.send = function () {
        // process the callback queue
        // the xhr instance is passed into each callback but seems pretty useless
        // you can't tell what its destination is or call abort() without an error
        // so only really good for logging that a request has happened
        // I could be wrong, I hope so...
        // EDIT: I suppose you could override the onreadystatechange handler though
        for (i = 0; i < XMLHttpRequest.callbacks.length; i++) {
          XMLHttpRequest.callbacks[i](this);
        }
        // call the native send()
        oldSend.apply(this, arguments);
      };
    }
  }

  // 监听通知请求
  const listenNotification = () => {
    addXMLRequestCallback(function (xhr) {
      xhr.addEventListener("load", function () {
        if (xhr.readyState == 4 && xhr.status == 200) {
          if (xhr.responseURL.includes("user/notifications")) {
            //do something!

            // 去重,将新通知添加到通知列表
            let $notice = JSON.parse(xhr.response);
            newNoticeList = $notice.slice(0, $notice.length - noticeLength);
            noticeLength = $notice.length;
            if (newNoticeList.length !== 0) {
              newNoticeList.forEach((item) => {
                if (item.type === "replyPost") {
                  noticeList.push({
                    pid: item.pid,
                    userName: item.name,
                    content: item.content,
                  });
                }
              });
            }
          }
        }
      });
    });
  };

  // 获取回复原数据
  function getOriginalData() {
    addXMLRequestCallback(function (xhr) {
      xhr.addEventListener("load", function () {
        if (xhr.readyState == 4 && xhr.status == 200) {
          if (xhr.responseURL.includes("holes/detail")) {
            //do something!

            originalreplyList = JSON.parse(xhr.response).replies;
            if (!originalreplyList?.length) {
              originalreplyList = [];
              throw new Error("获取源数据失败或没有回复");
            }
          }
        }
      });
    });
  }

  // 获取一段文字中所有大写字母
  function getUpperCase(str) {
    let arr = [];
    for (let i = 0; i < str.length; i++) {
      if (str[i] === str[i].toUpperCase() && str[i] !== " ") {
        arr.push(str[i]);
      }
    }
    return arr.join("");
  }

  // 检测是否加载完成,异步调用
  const checkLoad = () =>
    new Promise((resolve, reject) => {
      let check = setInterval(() => {
        let targetNode = document.querySelector(
          "#root > div > div > div > div > div > div > div > div > div.css-1dbjc4n.r-13awgt0 > div > div.css-1dbjc4n.r-1p0dtai.r-1d2f490.r-12vffkv.r-u8s1d.r-zchlnj.r-ipm5af > div.css-1dbjc4n.r-13awgt0.r-12vffkv > div > div > div > div > div > div.css-1dbjc4n.r-1kihuf0.r-e7q0ms"
        );

        if (targetNode !== null) {
          clearInterval(check);
          resolve(true);
        }
      }, 200);
    });

  // 获取回复
  const getReply = () => {
    const targetNode = document.getElementsByClassName(
      "css-1dbjc4n r-1kihuf0 r-knv0ih r-e7q0ms"
    );

    if (!targetNode) {
      throw new Error("获取回复失败");
    }

    hideUser.hideReplay();

    return targetNode;
  };

  // 监听楼层数量的变化
  const listenReplayCount = () => {
    let targetNode = document.querySelector(
      "#root > div > div > div > div > div > div > div > div > div.css-1dbjc4n.r-13awgt0 > div > div.css-1dbjc4n.r-1p0dtai.r-1d2f490.r-12vffkv.r-u8s1d.r-zchlnj.r-ipm5af > div.css-1dbjc4n.r-13awgt0.r-12vffkv > div > div > div > div > div > div.css-1dbjc4n.r-1kihuf0.r-13awgt0.r-knv0ih.r-13qz1uu > div > div > div:nth-child(2)"
    );

    const config = { childList: true };

    const callBack = (mutationsList, observer) => {
      replyNodes = getReply();
    };

    const observer = new MutationObserver(callBack);

    observer.observe(targetNode, config);
  };

  // 点击只看洞主
  const clickDZ = (e) => {
    if (originalreplyList?.length === 0) return;

    if (hideUser.onlySeeUserName === "") {
      e.target.classList.add("active");
      e.target.innerHTML = `只看洞主`;
      hideUser.onlySee("洞主");
    } else {
      e.target.classList.remove("active");
      e.target.innerHTML = `只看${hideUser.onlySeeUserName}`;
      hideUser.onlySee("");
    }

    hideUser.hideReplay();
  };

  // 查看所有图片
  const getImg = () => {
    let imgList = [];
    // 提取图片
    originalreplyList.forEach((item) => {
      if (item?.image) {
        imgList.push(`https://img.treehollow.net/${item.image.src}`);
      }
    });

    return imgList;
  };

  // 新增图片显示窗口
  const addImgWindow = () => {
    let container = document.createElement("div");
    container.classList = "img-container";
    container.style.cssText =
      "position: fixed; inset: 0px; z-index: 9999; background-color: rgba(0, 0, 0, 0.6); overflow-y: scroll; display: grid; grid-template-columns: 1fr 1fr 1fr; width: 100vw; height: 100vh;gap: 16px;";

    document.body.appendChild(container);
    let imgList = getImg();
    for (let i = 0; i < imgList.length; i++) {
      const imgUrl = imgList[i];
      let img = document.createElement("img");
      img.src = imgUrl;
      img.style.cssText = "width: 100%;";
      container.appendChild(img);
    }

    let close = document.createElement("div");
    close.innerText = "关闭";
    close.style.cssText =
      " position: fixed; right: 20px; bottom: 50%; margin-bottom: 16px; background-color: #53A13C; border-radius: 5px;  cursor: pointer; display: inline-block; font-size: 17px; font-weight: 400;;;; width: 50px;line-height: 1.2; padding: 17px 14px; text-align: center; text-decoration: none; color: #fff;";
    close.addEventListener("click", () => {
      container.remove();
    });
    container.appendChild(close);
  };

  // 回到顶部
  const goTop = () => {
    let targetNode = document.getElementsByClassName(
      "css-1dbjc4n r-1kihuf0 r-1x0uki6 r-e7q0ms"
    );
    targetNode[0].scrollIntoView({ behavior: "smooth" });
  };

  // 格式化回复
  const formatReply = (replyNode) => {
    let content =
      replyNode
        .getElementsByClassName(
          "css-901oao r-jwli3a r-ubezar r-13uqrnb r-16dba41 r-oxtfae r-dhbnww r-1xnzce8"
        )[0]
        ?.innerText.trim() || "";
    let userName = replyNode
      .getElementsByClassName(
        "css-901oao r-5rif8m r-ubezar r-13uqrnb r-majxgm r-oxtfae r-dhbnww r-13hce6t r-14gqq1x"
      )[0]
      ?.innerText.trim();
    let reply =
      replyNode
        .getElementsByClassName(
          "css-901oao css-vcwn7f r-5rif8m r-1b43r93 r-13uqrnb r-16dba41 r-oxtfae r-dhbnww r-1jkjb r-icoktb"
        )[0]
        ?.innerText.trim() || "";

    if (!userName) {
      throw new Error("格式化回复信息失败");
    }

    return {
      content,
      userName,
      reply,
    };
  };

  // 隐藏指定用户按钮
  const hideUser = {
    className: "hide-user",
    userList: [], // 隐藏用户名列表
    contentList: [], // 隐藏文本列表
    onlySeeUserName: "", // 只看指定用户
    isShow: false, // 是否显示列表界面

    // param {string} val 文本内容
    // param {string} type 类型 content/user
    addToHideList: (val, type) => {
      type === "content"
        ? hideUser.contentList.push(val.trim())
        : hideUser.userList.push(val.trim());
    },
    removeFromHideList: (val, type) => {
      type === "content"
        ? hideUser.contentList.splice(
            hideUser.contentList.indexOf(val.trim()),
            1
          )
        : hideUser.userList.splice(hideUser.userList.indexOf(val.trim()), 1);
    },
    // 新建隐藏列表窗口
    addWindow: () => {
      let container = document.createElement("div");
      container.className = "hide-container";
      container.style.cssText =
        "position: absolute;  z-index: 9999; background-color: rgba(0, 0, 0, 0.6); overflow-y: scroll; width: 260px; max-height: 230px; gap: 16px; padding: 16px;right: 120px;top: 30%; color: #fff;";

      document.body.appendChild(container);

      hideUser.addUserListDOM();
      hideUser.addContentListDOM();
    },
    // 移除隐藏列表窗口
    removeWindow: () => {
      let container = document.getElementsByClassName("hide-container")[0];
      if (container !== undefined) {
        container.remove();
      }
    },
    // 新增隐藏用户列表
    addUserListDOM: () => {
      let container = document.getElementsByClassName("hide-container")[0];
      if (container === undefined) {
        hideUser.addWindow();
        container = document.getElementsByClassName("hide-container")[0];
      }

      let userList = document.createElement("div");
      userList.style.cssText =
        "display: flex; flex-direction: column;align-items: flex-start; color: #fff; fontsize: 24px; font-weight: 600; margin-bottom: 16px;";
      userList.innerHTML = "被隐藏的用户(当前贴有效)";
      for (let i = 0; i < hideUser.userList.length; i++) {
        const userName = hideUser.userList[i];
        let element = document.createElement("div");
        element.innerText = `${userName} --- 点击移除`;
        element.style.cssText =
          "display: inline-block; font-size: 16px; font-weight: 400; margin-top: 8px; cursor: pointer;";
        element.addEventListener("click", () => {
          hideUser.removeFromHideList(userName, "user");
          element.remove();
          hideUser.hideReplay();
        });
        userList.appendChild(element);
      }

      // 输入框
      let input = document.createElement("input");
      input.style.cssText =
        "width: 100%; height: 40px; border-radius: 4px; border: 1px solid #fff; margin-bottom: 16px;";
      input.placeholder = "输入用户名首字母 如:AD";
      input.addEventListener("keyup", (e) => {
        if (e.keyCode === 13) {
          let name = input.value.trim();
          hideUser.addToHideList(name, "user");

          // 添加用户名到列表
          let element = document.createElement("div");
          element.innerText = `${name} --- 点击移除`;
          element.style.cssText =
            "display: inline-block; font-size: 16px; font-weight: 400; margin-top: 8px; cursor: pointer;";
          element.addEventListener("click", () => {
            hideUser.removeFromHideList(name, "user");
            element.remove();
            hideUser.hideReplay();
          });
          userList.insertBefore(element, input);

          hideUser.hideReplay();
          input.value = "";
        }
      });
      userList.appendChild(input);

      container.appendChild(userList);
    },
    // 新增隐藏文本列表
    addContentListDOM: () => {
      let container = document.getElementsByClassName("hide-container")[0];
      if (container === undefined) {
        hideUser.addWindow();
        container = document.getElementsByClassName("hide-container")[0];
      }

      let contentList = document.createElement("div");
      contentList.style.cssText =
        "display: flex; flex-direction: column;align-items: flex-start; color: #fff; fontsize: 24px; font-weight: 600; margin-bottom: 16px;";
      contentList.innerHTML = "被隐藏的文本";
      for (let i = 0; i < hideUser.contentList.length; i++) {
        const content = hideUser.contentList[i];
        let element = document.createElement("div");
        element.innerText = `${content} --- 点击移除`;
        element.style.cssText =
          "display: inline-block; font-size: 16px; font-weight: 400; margin-top: 8px; cursor: pointer;";
        element.addEventListener("click", () => {
          hideUser.removeFromHideList(content, "content");
          element.remove();
          hideUser.hideReplay();
        });
        contentList.appendChild(element);
      }

      // 输入框
      let input = document.createElement("input");
      input.style.cssText =
        "width: 100%; height: 40px; border-radius: 4px; border: 1px solid #fff; margin-bottom: 16px;";
      input.placeholder = "输入文本 如:cy";
      input.addEventListener("keyup", (e) => {
        if (e.keyCode === 13) {
          let content = input.value.trim();
          hideUser.addToHideList(content, "content");

          // 添加文本到列表
          let element = document.createElement("div");
          element.innerText = `${content} --- 点击移除`;
          element.style.cssText =
            "display: inline-block; font-size: 16px; font-weight: 400; margin-top: 8px; cursor: pointer;";
          element.addEventListener("click", () => {
            hideUser.removeFromHideList(content, "content");
            element.remove();
            hideUser.hideReplay();
          });
          contentList.insertBefore(element, input);
          hideUser.hideReplay();
          input.value = "";
        }
      });
      contentList.appendChild(input);

      container.appendChild(contentList);
    },

    // 执行只看某人
    onlySee: (val = "洞主") => {
      if (hideUser.onlySeeUserName === "") {
        hideUser.onlySeeUserName = val.trim();
      } else {
        hideUser.onlySeeUserName = "";
      }
    },
    // 检查是否需要隐藏
    checkHide: (replyNode) => {
      if (originalreplyList?.length === 0) return false;

      let { content, userName } = formatReply(replyNode);

      if (hideUser.onlySeeUserName !== "") {
        // 只看指定用户
        if (userName !== hideUser.onlySeeUserName) {
          return true;
        }
      } else {
        if (
          hideUser.userList.includes(getUpperCase(userName)) ||
          hideUser.contentList.includes(content)
        ) {
          return true;
        }
      }

      return false;
    },
    // 隐藏内容
    hideReplay: () => {
      for (let i = 0; i < replyNodes.length; i++) {
        if (hideUser.checkHide(replyNodes[i])) {
          replyNodes[i].style.display = "none";
        } else {
          replyNodes[i].style.display = "block";
        }
      }
    },

    clickHandler: (e) => {
      hideUser.isShow = !hideUser.isShow;
      hideUser.isShow ? hideUser.addWindow() : hideUser.removeWindow();
      hideUser.isShow
        ? e.target.classList.add("active")
        : e.target.classList.remove("active");
    },
  };

  // 显示/隐藏button-container
  const showMenu = {
    isShow: false,
    show: (e) => {
      showMenu.isShow = true;
      let container = document.getElementsByClassName("button-container")[0];
      for (let i = 0; i < container.childNodes.length - 1; i++) {
        const element = container.childNodes[i];
        element.style.transform = "translateX(0)";
      }
      if (e) {
        e.target.innerText = "关闭菜单";
      }
    },
    hide: (e) => {
      showMenu.isShow = false;
      let container = document.getElementsByClassName("button-container")[0];
      for (let i = 0; i < container.childNodes.length - 1; i++) {
        const element = container.childNodes[i];
        element.style.transform = "translateX(100%)";
        element.style.transition = "all 0.3s ease-in-out";
      }
      if (e) {
        e.target.innerText = "显示菜单";
      }
    },
    clickHandler: (e) => {
      if (showMenu.isShow) {
        showMenu.hide(e);
        showMenu.isShow = false;
      } else {
        showMenu.show(e);
        showMenu.isShow = true;
      }
    },
  };

  // button 相关
  const button = {
    // 添加按钮
    addButton: (text, callback = () => {}, className = "") => {
      let pcCss = {
        container:
          "display: flex; justify-content: center; align-items: center; position: fixed; right: 20px; bottom: 50%;flex-direction: column;transform: translateY(55%);",
        button:
          "z-index: 9999; margin-bottom: 16px; background-color: #53A13C; border-radius: 5px;  cursor: pointer; display: inline-block; font-size: 17px; font-weight: 400;;;; width: 50px;line-height: 1.2; padding: 17px 14px; text-align: center; text-decoration: none; color: #fff;",
      };

      let mobileCss = {
        container:
          "display: flex; justify-content: center; align-items: center; position: fixed; right: 10px; bottom: 30%; flex-direction: column; transform: translateY(30%);",
        button:
          "z-index: 9999; margin-bottom: 16px; background-color: rgb(83, 161, 60); border-radius: 5px; cursor: pointer; display: inline-block;  font-weight: 400; width: 35px; line-height: 1.2; padding: 10px 9px; text-align: center; text-decoration: none; color: rgb(255, 255, 255);font-size: 12px;",
      };

      let container = document.getElementsByClassName("button-container")[0];
      if (container === undefined) {
        // 添加container
        container = document.createElement("div");
        container.className = `button-container ${className}`;
        container.style.cssText = isMobile()
          ? mobileCss.container
          : pcCss.container;
        document.body.appendChild(container);
      }

      const element = document.createElement("div");
      element.innerText = text;
      element.className = "dz-button";
      element.style.cssText = isMobile() ? mobileCss.button : pcCss.button;

      element.addEventListener("click", callback);
      container.appendChild(element);
    },

    // 移除按钮
    removeButton: () => {
      let container = document.getElementsByClassName("button-container")[0];
      if (container !== undefined) {
        container.remove();
      }
    },
  };

  // 新建button
  const addButton = (name, clickCallBack, className = "") => {
    button.addButton(name, clickCallBack, className);
  };

  /**
   * 重写history的pushState和replaceState
   * @param action pushState|replaceState
   * @return {function(): *}
   */
  function wrapState(action) {
    // 获取原始定义
    let raw = history[action];
    return function () {
      // 经过包装的pushState或replaceState
      let wrapper = raw.apply(this, arguments);

      // 定义名为action的事件
      let e = new Event(action);

      // 将调用pushState或replaceState时的参数作为stateInfo属性放到事件参数event上
      e.stateInfo = { ...arguments };
      // 调用pushState或replaceState时触发该事件
      window.dispatchEvent(e);
      return wrapper;
    };
  }
  //修改原始定义
  history.pushState = wrapState("pushState");
  history.replaceState = wrapState("replaceState");

  // 初始化数据
  const initData = () => {
    button.removeButton();
    hideUser.removeWindow();
    hideUser.onlySeeUserName = "";
    if (window.location.pathname == "/HoleDetail") {
      getOriginalData();
      checkLoad().then((res) => {
        replyNodes = getReply();
        listenReplayCount();
        button.removeButton();

        addButton("只看洞主", clickDZ);
        addButton("下载图片", addImgWindow);
        addButton("回到顶部", goTop);
        addButton("隐藏用户", hideUser.clickHandler);
        addButton("显示菜单", showMenu.clickHandler);
        showMenu.hide();
      });
    }
    if (
      window.location.pathname !== "/HoleDetail" &&
      window.location.pathname !== "/ReplyModal"
    ) {
      hideUser.userList = [];
    }
  };

  // 监听自定义的事件
  window.addEventListener("pushState", function (e) {
    initData();
  });

  window.addEventListener("replaceState", function (e) {
    initData();
  });
})();