Greasy Fork 支持简体中文。

NodeSeek+

load post detail information is automatically loaded when the button is clicked

// ==UserScript==
// @name         NodeSeek+
// @namespace    http://tampermonkey.net/
// @version      0.4.2
// @description  load post detail information is automatically loaded when the button is clicked
// @author       tsd
// @match        https://www.nodeseek.com/*
// @match        https://www.nodeseek.com/*
// @icon         https://www.nodeseek.com/static/image/favicon/android-chrome-192x192.png
// @license      GPLv3
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_removeValueChangeListener
// @grant        GM_addValueChangeListener
// @grant    GM_registerMenuCommand
// @grant    GM_unregisterMenuCommand

// ==/UserScript==

(function () {
  "use strict";
  console.log("script");

  //GM_setValue
  //allCollectionData   收藏的post的id列表
  //checkInTime         签到的时间
  //isRandom            是否摸奖签到
  //isAuto              是否自动签到
  //contentTime         记录点击展开回复内容div的时间
  //replyNum            当前回复数
  //blockWords          屏蔽的关键词

  //默认抽奖
  if (GM_getValue("isRandom") === undefined) {
    GM_setValue("isRandom", true);
  }
  //默认手动
  if (GM_getValue("isAuto") === undefined) {
    GM_setValue("isAuto", false);
  }

  //注册菜单
  let switchCheckType;
  let switchAutoType;
  let listenerswitchCheckType;
  let listenerswitchAutoType;
  function createMenu() {
    switchCheckType = GM_registerMenuCommand(
      GM_getValue("isRandom") === true
        ? "切换签到模式,当前为随机"
        : "切换签到模式,当前为固定",
      menuRandomClick
    );
    switchAutoType = GM_registerMenuCommand(
      GM_getValue("isAuto") === true
        ? "切换自动模式,当前为自动"
        : "切换自动模式,当前为手动",
      menuAutoClick
    );
    //是否有变动
    listenerswitchCheckType = GM_addValueChangeListener(
      "isRandom",
      function (name, old_value, new_value, remote) {
        if (old_value !== new_value) {
          mscAlert(new_value === true ? "已切换为随机" : "已切换为固定");
        }
      }
    );
    listenerswitchAutoType = GM_addValueChangeListener(
      "isAuto",
      function (name, old_value, new_value, remote) {
        if (old_value !== new_value) {
          mscAlert(new_value === true ? "已切换为自动" : "已切换为手动");
        }
      }
    );
  }
  //菜单点击刷新签到信息
  function menuRandomClick() {
    GM_unregisterMenuCommand(switchCheckType);
    GM_getValue("isRandom") === true
      ? GM_setValue("isRandom", false)
      : GM_setValue("isRandom", true);
    //重新注册
    GM_removeValueChangeListener(listenerswitchCheckType);
    GM_removeValueChangeListener(listenerswitchAutoType);
    createMenu();
    checkIn();
  }
  //菜单点击刷新自动信息
  function menuAutoClick() {
    GM_unregisterMenuCommand(switchAutoType);
    GM_getValue("isAuto") === true
      ? GM_setValue("isAuto", false)
      : GM_setValue("isAuto", true);
    //重新注册
    GM_removeValueChangeListener(listenerswitchCheckType);
    GM_removeValueChangeListener(listenerswitchAutoType);
    createMenu();
    checkIn();
  }

  // 检查是否登陆
  let loginStatus = false;
  // 查看手机情况
  let mobileStatus = false;
  if (document.querySelector(".user-head")) {
    loginStatus = true;
  }
  // 检查屏蔽关键词
  blockPost();

  if(!document.querySelector("#nsk-right-panel-container>.user-card")){
    mobileStatus = true;
  }

  if (loginStatus) {
    //注册油猴菜单
    createMenu();
    //处理登录;
    checkIn();
    //维护一个全部的收藏id列表
    if (GM_getValue("allCollectionData") === undefined) {
      loadUntilEmpty();
    }
  } else {
    //清除收藏的post的id列表
    //GM_deleteValue("allCollectionData");
    //清除签到的时间
    //GM_deleteValue("checkInTime");
    //清除签到方式
    //GM_deleteValue("isRandom");
    //清除自动方式
    //GM_deleteValue("isAuto");
  }

  //签到判断
  function checkIn() {
    let timeNow =
      new Date().getFullYear() +
      "/" +
      (new Date().getMonth() + 1) +
      "/" +
      new Date().getDate();
    let oldTime = GM_getValue("checkInTime");
    if (!oldTime || oldTime !== timeNow) {
      //允许执行签到
      if (GM_getValue("isAuto") === true) {
        //执行自动签到
        getChicken(GM_getValue("isRandom")).then((responseData) => {
          if (responseData.success === true) {
            //签到成功之后存下时间
            GM_setValue("checkInTime", timeNow);
            console.log(`[NodeSeek] 签到:`, responseData.message);
          } else if (responseData.message === "今天已完成签到,请勿重复操作") {
            //存下时间
            GM_setValue("checkInTime", timeNow);
            console.log(`[NodeSeek] 签到:`, responseData.message);
          } else {
            console.error("Error in checkIn:", error);
          }
        });
      } else {
        //执行手动签到
        //处理按钮
        let right_panel = document.querySelector("#nsk-right-panel-container");
        let new_check = document.createElement("div");
        let publish_btn = document.querySelector(".btn.new-discussion");
        let publish_btn_parent = publish_btn.parentNode;
        new_check.innerHTML = '<a class="btn new-discussion">签到</a>';
        //展示签到按钮
        right_panel.insertBefore(new_check, publish_btn_parent);
        setupCursorStyle(new_check);
        new_check.onclick = function () {
          getChicken(GM_getValue("isRandom")).then((responseData) => {
            console.log(responseData.message);
            if (responseData.success === true) {
              //签到成功之后存下时间
              GM_setValue("checkInTime", timeNow);
              //弹窗示意多少鸡腿
              mscAlert(responseData.message);
              //隐藏
              new_check.style.display = "none";
            } else if (
              responseData.message === "今天已完成签到,请勿重复操作"
            ) {
              //存下时间
              GM_setValue("checkInTime", timeNow);
              mscAlert(responseData.message);
              //隐藏
              new_check.style.display = "none";
            } else {
              console.error("Error in checkIn:", error);
            }
          });
        };
      }
    } else {
      //返回已经签到按钮或隐藏
    }
  }
  // 签到
  async function getChicken(random) {
      const url = `https://www.nodeseek.com/api/attendance?random=${random}`;
      const data = {
      random: random,
    };
    try {
      const responseData = await postData(url, data);
      return responseData;
    } catch (error) {
      console.error("Error in getChicken:", error);
      return null;
    }
  }
  //自定义屏蔽词窗口
  let modal = document.createElement('div');
  let modalContent = document.createElement('div');
  let closeBtn = document.createElement('span');
  let promptText = document.createElement('p');
  let inputField = document.createElement('input');
  let submitBtn = document.createElement('button');

  // 设置元素属性和内容
  modal.id = 'myModal';
  modal.className = 'modal';
  modalContent.className = 'modal-content';
  closeBtn.className = 'close';
  closeBtn.textContent = '×';
  promptText.textContent = '请输入要屏蔽的关键词,多个关键词用逗号隔开(注意:严格区分,最好是复制过来)';
  inputField.id = 'block-input';
  inputField.type = 'text';
  submitBtn.id = 'submit-button';
  submitBtn.textContent = '确定';

  // 将所有元素添加到屏蔽词窗口中
  modalContent.appendChild(closeBtn);
  modalContent.appendChild(promptText);
  modalContent.appendChild(inputField);
  modalContent.appendChild(submitBtn);
  modal.appendChild(modalContent);

  function createBlockWordsModal() {
    document.body.appendChild(modal);
    modal.style.display = "block";
  }
  // 自定义屏蔽词
  let blockDiv = document.querySelector(".sorter");
  let btnBlock = document.createElement("button");
  btnBlock.style.cursor = 'pointer';
  btnBlock.innerText = "关键词屏蔽";
  btnBlock.classList.add("btnBlock-post");
  if(blockDiv !== null){
    blockDiv.parentNode.insertBefore(btnBlock, blockDiv.nextSibling);
  }


  // 弹窗输入屏蔽词
  function getOldBlockWords() {
    let oldBlockWords = GM_getValue("blockWords");
    inputField.value = oldBlockWords ? oldBlockWords : '';
    modal.style.display = 'block';
  }

  // 当用户点击 "确定" 按钮,获取输入值
  submitBtn.onclick = function() {
    let blockWords = inputField.value.trim();
    GM_setValue("blockWords", blockWords);
    modal.style.display = 'none';
    if (!blockWords) {
      window.location.reload();
    } else {
      blockPost();
    }
  }

  // 当用户点击屏蔽词窗口外的任何地方或 "x" 图标,关闭屏蔽词窗口
  closeBtn.onclick = window.onclick = function(event) {
    if (event.target == modal || event.target == closeBtn) {
      modal.style.display = 'none';
    }
  }

  // 点击按钮时,显示屏蔽词窗口
  btnBlock.onclick = function () {
    createBlockWordsModal();
    getOldBlockWords();
  };

  //屏蔽帖子
  function blockPost() {
    //获取自定义的屏蔽词
    let blockWords = GM_getValue("blockWords");
    blockWords = blockWords === undefined ? '':blockWords.trim();
    if (blockWords) {
      let blockWordsArr = blockWords.split(",");
      let lists = document.querySelectorAll(".post-list");
      lists.forEach((list) => {
        let items = list.childNodes;
        items.forEach((element) => {
          let post_item = element.querySelector(".post-title>a");
          let post_title = post_item.innerText;
          blockWordsArr.forEach((word) => {
            if (post_title.includes(word)) {
              element.classList.add('blocked-post');
            }
          });
        });
      });
    }
  }

  // 查看内容时的标签顺序样式修改
  let footTag = document.querySelectorAll(".floor-link");
  footTag.forEach((item) =>{
    item.className = "foot-tag";
    let itemChild = item.firstChild.textContent;
    item.firstChild.textContent = itemChild.replace(/^#/, "")
  })




  //查看帖子中的回复消息
  initializePage();

  // 定义一个函数来发送GET请求
  async function loadData(page) {
    const url = `https://www.nodeseek.com/api/statistics/list-collection?page=${page}`;
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const data = await response.json();
      return data;
    } catch (error) {
      console.error("Error:", error);
      return null;
    }
  }
  //POST请求
  async function postData(url = "", data = {}) {
    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(data),
      });
      const responseData = await response.json();
      return responseData;
    } catch (error) {
      console.error("Error in postData:", error);
    }
  }
  async function loadUntilEmpty(page = 1) {
    //收藏列表数组
    let allCollectionData = [];
    while (true) {
      const data = await loadData(page);
      data.collections.forEach((item) => {
        // 将获取到的数据加到数组中
        allCollectionData.push(item.post_id);
      });
      // 如果没有获取到数据或获取到的数据为空,停止加载
      if (!data || data.collections.length === 0) {
        //丢进去方便存取
        GM_setValue("allCollectionData", allCollectionData);
        break;
      }
      page++;
    }
  }

  function initializePage() {
    let lists = document.querySelectorAll(".post-list");
    lists.forEach((list) => {
      let items = list.childNodes;
      items.forEach((element) => {
        setupPostItem(element);
      });
    });
  }

  function setupPostItem(element) {
    let post_item = element.querySelector(".post-title>a");
    let new_div = document.createElement("span");
    if(mobileStatus){
      new_div.className = "info-triganle-mobile";
    }else{
      new_div.className = "info-triganle";
    }
    new_div.innerHTML = '<span class="triangle">▼</span>';
    element.querySelector(".post-info").append(new_div);
    setupCursorStyle(new_div);
    new_div.onclick = function () {
      if(GM_getValue("contentTime") + 1000 >= Date.now()){
        console.warn("请勿重复点击");
      }else{
        GM_setValue("contentTime",Date.now());
        GM_setValue("replyNum",element.querySelector(".info-item.info-comments-count > span").innerText);
        togglePostContent(post_item, element, new_div);
      }
    };
  }

  function togglePostContent(post_item, element, new_div) {
    let id = post_item.href.replace("https://www.nodeseek.com", "");
    let content = document.getElementById(id);
    if (content) {
      toggleDisplay(content, new_div);
    } else {
      new_div.firstElementChild.innerText = "○";
      document.body.style.cursor = "wait";
      new_div.firstElementChild.className = "content-loaded";
      fetchContent(post_item.href, element, (contents, targetEle) => {
        insertContentAfter(contents, targetEle);
        loadNextPage(contents, targetEle, 1);
        new_div.firstElementChild.innerText = "▲";
        document.body.style.cursor = "auto";
      });
    }
  }
  //显隐箭头
  function toggleDisplay(content, new_div) {
    if (content.style.display === "none") {
      content.style.display = "block";
      new_div.firstElementChild.innerText = "▲";
    } else {
      content.style.display = "none";
      new_div.firstElementChild.innerText = "▼";
    }
  }

  //获取div框中的内容
  function fetchContent(url, targetEle, callback) {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onload = function () {
        if (xhr.status !== 200) return;

        const tempContainer = document.createElement("div");
        tempContainer.innerHTML = xhr.responseText;
        const contents = createContentDiv(url);
        const post_contents = tempContainer.querySelectorAll(".post-content");
        const colloct = appendPostContentBox(contents);

        post_contents.forEach((e) => {
            modifyFootTagDivStyle(e);
            contents.firstChild.appendChild(e.parentElement);
        });

        if (callback && typeof callback === "function") {
            callback(contents, targetEle);
        }
    };
    xhr.send();
}

function createContentDiv(url) {
    const contents = document.createElement("div");
    contents.id = url.replace("https://www.nodeseek.com", "");
    contents.className = "content-div";
    return contents;
}

function appendPostContentBox(contents) {
    contents.innerHTML += '<div class="post-content-box"></div>';

    const colloct = contents.firstChild;
    colloct.innerHTML +=
        '<div data-v-372de460="" class="comment-menu">' +
        '<div data-v-372de460="" title="收藏" class="menu-item"><svg data-v-372de460="" class="iconpark-icon"><use data-v-372de460="" href="#star-6negdgdk"></use></svg></div>' +
        "</div>";

    const icon = colloct.firstElementChild.querySelector(".menu-item");

    const postId = getPostId(contents.id);
    const is_collected = GM_getValue("allCollectionData").some(
        (item) => item === postId
    );
    if (is_collected) {
        icon.style.color = "red";
    }

    setupCursorStyle(icon);
    icon.onclick = function () {
        colloctContent(postId, colloct);
    };
    return colloct;
}

function getPostId(id) {
    const regex = /\/post-(\d+)-1/;
    const match = id.match(regex);
    if (match != null) {
        return parseInt(match[1]);
    }
    return null;
}

function modifyFootTagDivStyle(e) {
    const footTagDiv = e.parentElement.querySelector(".floor-link");
    footTagDiv.className = "foot-tag-div";
    const itemChild = footTagDiv.textContent;
    footTagDiv.textContent = itemChild.replace(/^#/, "")
}

  //帖子的收藏处理
  function colloctContent(post_id, colloct) {
    let icon = colloct.firstElementChild.querySelector(".menu-item");
    if (icon.style.color === "red") {
      //取消收藏处理
      let result = confirm("您确定要取消收藏吗?");
      if (result) {
        collection_del("remove", post_id).then((success) => {
          if (success === true) {
            icon.style.color = "";
            //维护一个全部的收藏id列表
            loadUntilEmpty();
          }
        });
      }
    } else {
      //收藏帖子
      collection_add("add", post_id).then((success) => {
        if (success === true) {
          icon.style.color = "red";
          //维护一个全部的收藏id列表
          loadUntilEmpty();
        }
      });
    }
  }
  //收藏方法
  async function collection_add(action_type, post_id) {
    const url = "https://www.nodeseek.com/api/statistics/collection";
    const data = {
      action: action_type,
      postId: post_id,
    };
    try {
      const responseData = await postData(url, data);
      if (responseData && responseData.success === true) {
        mscAlert("收藏成功!");
      }
      //   else if (responseData && responseData.success === false) {
      //     alert("你已经收藏过了!");
      //   }
      return responseData ? responseData.success : null;
    } catch (error) {
      console.error("Error in collection_add:", error);
      return null;
    }
  }

  //取消收藏方法
  async function collection_del(action_type, post_id) {
    const url = "https://www.nodeseek.com/api/statistics/collection";
    const data = {
      action: action_type,
      postId: post_id,
    };
    try {
      const responseData = await postData(url, data);
      if (responseData && responseData.success === true) {
        mscAlert("取消收藏成功!");
      }
      return responseData ? responseData.success : null;
    } catch (error) {
      console.error("Error in collection_del:", error);
      return null;
    }
  }

  function insertContentAfter(content, targetEle) {
    let ul = targetEle.parentNode;
    ul.insertBefore(content, targetEle.nextSibling);
  }

  function loadNextPage(contentDiv, targetEle, currentPage) {
    if(GM_getValue("replyNum") / 10 <= currentPage){
      return;
    }
    let nextPage = currentPage + 1;
    let nextPageUrl = targetEle
      .querySelector(".post-title>a")
      .href.replace(/(\d+)$/, nextPage);
    fetchContent(nextPageUrl, targetEle, (nextContents, targetEle) => {
      let postContentBox = contentDiv.querySelector(".post-content-box");
      if (nextContents.querySelector(".post-content")) {
        let nextPostContents = nextContents.querySelectorAll(".post-content");
        nextPostContents.forEach((e) => {
          postContentBox.appendChild(e.parentElement);
        });
        // 递归调用以加载后续页面,延迟1秒
        setTimeout(() => {
          loadNextPage(contentDiv, targetEle, nextPage);
        }, 1000);
      }
    });
  }

  function setupCursorStyle(element) {
    element.addEventListener("mouseover", function () {
      document.body.style.cursor = "pointer";
    });
    element.addEventListener("mouseout", function () {
      document.body.style.cursor = "auto";
    });
  }

  let css = `
     .content-div {
        height: 600px;
        padding: 20px;
        margin: 10px auto;
        border: 1px solid gray;
        border-radius: 10px;
        overflow: scroll;
    }

    .post-content-box {
        border-bottom: 2px dashed gray;
        padding-bottom: 10px;
        margin-bottom: 10px;
    }

    .triangle {
        font-size: medium;
        color: gray;
    }
    .info-triganle{
        position: absolute;
        right: 54px;
    }
    .info-triganle-mobile{
      position: absolute;
    }
    .content-loaded {
        font-size: medium;
        color: red;
    }
    ::-webkit-scrollbar {
        width: 6px;
        height: 6px;
    }
    ::-webkit-scrollbar-track {
        border-radius: 3px;
        background: rgba(0,0,0,0.06);
        -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.08);
    }
    ::-webkit-scrollbar-thumb {
        border-radius: 3px;
        background: rgba(0,0,0,0.12);
        -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.2);
    }
    .foot-tag{
      margin-left: 1rem;
      line-height: 0.5rem;
      border-radius: 0.5rem;
      display: inline-block;
      background-color: #f0f0f0;
      color: #bdbdbd;
      padding: 3px 9px;
      cursor: default;
    }
    .foot-tag-div{
      margin-left: 1rem;
      line-height: 0.5rem;
      border-radius: 0.5rem;
      display: inline-block;
      background-color: #f0f0f0;
      color: #bdbdbd;
      padding: 3px 9px;
      cursor: default;
    }
    .preview {
      margin: 1rem 0;
      border: 1px solid transparent;
      border-radius: 8px;;
      cursor: pointer;
    }

    .preview:hover {
      border: 1px solid #c8c8c8;
    }

    .preview > .post-content {
      height: 200px !important;
      margin-top: 0.5rem !important;
    }

    .preview > .post-content.show-all {
      max-height: 200px;
      -webkit-mask-image:none;
    }

    .preview  .topic-link:link {
      color: black !important;
    }
    .btnBlock-post{
      background-color: #888;
      border: 1px solid #737373;
      border-radius: 3px;
      display: inline-flex;
      margin: 0 8px;
      position: absolute;
      color: var(--bg-main-color);
      left: 15%;
    }
    /* 屏蔽框 */
    .modal {
      display: none;
      position: fixed;
      z-index: 1;
      padding-top: 100px;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      overflow: auto;
      background-color: rgba(0,0,0,0.4);
    }
    .modal-content {
      height: 15%;
      background-color: #fefefe;
      margin: auto;
      padding: 20px;
      border: 1px solid #888;
      width: 60%;
      border-radius: 15px;
      box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19);
    }
    .close {
      color: #aaaaaa;
      float: right;
      font-size: 28px;
      font-weight: bold;
    }
    .close:hover,
    .close:focus {
      color: #000;
      text-decoration: none;
      cursor: pointer;
    }
    .modal p {
      font-size: 18px;
      font-weight: bold;
    }
    #block-input {
      width: 100%;
      padding: 12px 20px;
      margin: 8px 0;
      box-sizing: border-box;
      border: 2px solid #ccc;
      border-radius: 4px;
      background-color: #f8f8f8;
      resize: none;
    }
    #submit-button {
      background-color: #4CAF50;
      color: white;
      padding: 10px 33px;
      text-align: center;
      text-decoration: none;
      display: block;
      font-size: 16px;
      margin: 8px 2px;
      cursor: pointer;
      border: none;
      border-radius: 4px;
      width: auto;
      float: right;
    }

    @media screen and (max-width: 600px) {
      .modal-content {
          width: 90%;
          height: 16%;
      }
      .btnBlock-post{
        background-color: #888;
        border: 1px solid #737373;
        border-radius: 3px;
        display: inline-flex;
        margin: 0 8px;
        position: absolute;
        color: var(--bg-main-color);
        left: 30%;
      }
    }
    `;
  GM_addStyle(css);
})();