NodeSeek+

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

目前為 2023-11-27 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 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.3.4
// @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            当前回复数

  //默认抽奖
  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) {
          alert(new_value === true ? "已切换为随机" : "已切换为固定");
        }
      }
    );
    listenerswitchAutoType = GM_addValueChangeListener(
      "isAuto",
      function (name, old_value, new_value, remote) {
        if (old_value !== new_value) {
          alert(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;
  }
  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);
              //弹窗示意多少鸡腿
              alert(responseData.message);
              //隐藏
              new_check.style.display = "none";
            } else if (
              responseData.message === "今天已完成签到,请勿重复操作"
            ) {
              //存下时间
              GM_setValue("checkInTime", timeNow);
              alert(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";
    const data = {
      random: random,
    };
    try {
      const responseData = await postData(url, data);
      return responseData;
    } catch (error) {
      console.error("Error in getChicken:", error);
      return null;
    }
  }

  //查看帖子中的回复消息
  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;
    }
  }
  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") + 500 >= 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框中的内容
  //TODO:重构一下这坨狗屎
  function fetchContent(url, targetEle, callback) {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onload = function () {
      if (xhr.status === 200) {
        const tempContainer = document.createElement("div");
        tempContainer.innerHTML = xhr.responseText;
        let contents = document.createElement("div");
        contents.id = url.replace("https://www.nodeseek.com", "");
        contents.className = "content-div";

        //content
        let post_contents = tempContainer.querySelectorAll(".post-content");
        contents.innerHTML += '<div class="post-content-box"></div>';
        //menu
        let colloct = contents.firstChild;
        colloct.innerHTML +=
          //TODO:免费鸡腿和like处理
          '<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="#chicken-leg"></use></svg><span data-v-372de460="">0</span></div>'
          //  +'<div data-v-372de460="" title="反对" class="menu-item"><svg data-v-372de460="" class="iconpark-icon"><use data-v-372de460="" href="#bad-one"></use></svg><span data-v-372de460="">0</span></div> '
          '<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 data-v-372de460="" class="menu-item"><svg data-v-372de460="" class="iconpark-icon"><use data-v-372de460="" href="#quote"></use></svg><span data-v-372de460="">引用</span></div> <!----> '
          //  +'<div data-v-372de460="" class="menu-item"><svg data-v-372de460="" class="iconpark-icon"><use data-v-372de460="" href="#back"></use></svg><span data-v-372de460="">回复</span></div> <!---->
          "</div>";
        //找收藏的id
        let regex = /\/post-(\d+)-1/;
        let match = contents.id.match(regex);
        //先将就着用
        let post_id;
        let is_collected;
        let icon;
        icon = colloct.firstElementChild.querySelector(".menu-item");
        if (match != null) {
          //当前展开的post的id
          post_id = match[1];
          post_id = parseInt(post_id);
          is_collected = GM_getValue("allCollectionData").some(
            (item) => item === post_id
          );
          //判断是否已收藏
          if (is_collected) {
            icon.style.color = "red";
          }
        }
        setupCursorStyle(icon);
        icon.onclick = function () {
          colloctContent(post_id, colloct);
        };
        post_contents.forEach((e) => {
          contents.firstChild.appendChild(e.parentElement);
        });

        if (callback && typeof callback === "function") {
          callback(contents, targetEle);
        }
      } else {
        return;
      }
    };
    xhr.onerror = function () {
      console.error("Network error occurred while loading content.");
    };
    xhr.send();
  }
  //帖子的收藏处理
  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) {
        alert("收藏成功!");
      }
      //   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) {
        alert("取消收藏成功!");
      }
      return responseData ? responseData.success : null;
    } catch (error) {
      console.error("Error in collection_del:", 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);
    }
  }

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

    `;
  GM_addStyle(css);
})();