// ==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 // @downloadURL https://update.greasyfork.icu/scripts/480376/NodeSeek%2B.user.js // @updateURL https://update.greasyfork.icu/scripts/480376/NodeSeek%2B.meta.js // ==/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 = '签到'; //展示签到按钮 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 = '▼'; 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 += '
'; const colloct = contents.firstChild; colloct.innerHTML += ' "; 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); })();