NodeSeek+

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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