NodeSeek增强

自动签到、自动滚动翻页

目前为 2024-02-23 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name NodeSeek增强
  3. // @namespace http://www.nodeseek.com/
  4. // @version 0.3-alpha
  5. // @description 自动签到、自动滚动翻页
  6. // @author dabao
  7. // @match *://www.nodeseek.com/*
  8. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACz0lEQVR4Ae3B32tVdQAA8M85u7aVHObmzJVD0+ssiphstLEM62CBlCBEIAYhUoGGD/kiRUo+9CIEElFZgZJFSApBVhCUX2WFrVQKf5Qy26SgdK4pN7eZu+cbtyfJ/gLx83HD9SAhlEyXupiPhUSTeonRfNw1ws2aRJeN5jHcolFhJJ9M8Zj99piDTnv12SjzfzIb9dmrC7Pttt8ykjDVLsu8ZZ1GH1oqeDofJLtJh4fMEw3Y72jlCuEO2+W+sNJFr3vOZ1YIi8NIGA29hDWhGgZDJ2Rt2ZvZSBazmMUsZsPZ1qwVQmcYDNWwhtAbRsNIWJx6WLPDfgxNVkm9nR8hm+XduLba7F9RtcXztmUzyY/YJrUqNPvBYc0eSS3CwXxMl4WG7CarsyEuvU2HOkRNujSw3PosxR6DFurKxx3E/akFohPo0aDfEO61os5LdrtLVWG1TzxokifdiSH9GnTjuGhBqsWE39GOo3kVi8wsmeVW00SJ200zA9r0kFcdQzv+MKElVW/S+L5EE86pmUth3BV/SzCOCUjMVXMWzfsSYybVl1SlSlESkagpuOI1nzshFX1gyAF1UKhJEKOkJFVNXVBv+pJoBK1qBkh86z1/SaR+9o5zEgoDaloxsiSart6F1Bkl83ESHWEKvvEbqZJETaokgSH9hCk6cBLtSs6kDqEb/cZ0K+MnO0X/VdhRGUBZjzH9uA+HUl+a0BvmO+J7bVZSKWz1kehqhfe9oWalNoccDmW9JnyV+toxsy3PK3aY9Gx4gMp567ziV4WawpCXra+MEhZ5xqTtecVycxzXlxA22OK4ZYbt9LjvrM5PkNUp6zVPdNpBv1QKwt126Paxp8zwqXu8kG8pYZdHlT2Rvxo2aVG2ObyYn65UnXLKVULZZrP02ZRfCms1OmAXCSHRYqrLzuZFaDFV6s/8omuERs0Kl/LzITVTvTHDeXTD9eAftAsSYhXYOWUAAAAASUVORK5CYII=
  9. // @require https://cdn.staticfile.org/notie/4.3.1/notie.min.js
  10. // @resource notieStyle https://cdn.staticfile.org/notie/4.3.1/notie.min.css
  11. // @resource highlightStyle https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css
  12. // @grant GM_xmlhttpRequest
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_notification
  16. // @grant GM_registerMenuCommand
  17. // @grant GM_unregisterMenuCommand
  18. // @grant GM_getResourceText
  19. // @grant GM_addElement
  20. // @grant GM_addStyle
  21. // @grant unsafeWindow
  22. // @run-at document-end
  23. // @license GPL-3.0 License
  24. // @supportURL https://www.nodeseek.com/notification#/message?mode=talk&to=8110
  25. // @homepageURL https://www.nodeseek.com/post-36263-1
  26. // ==/UserScript==
  27.  
  28. (function () {
  29. 'use strict';
  30.  
  31. const util = {
  32. clog(c) {
  33. console.group("%c %c [NodeSeek增强]", `background:url(${GM_info.script.icon}) center center no-repeat;background-size:12px;padding:3px`, "");
  34. console.log(c);
  35. console.groupEnd();
  36. },
  37. parseQuery(name) {
  38. let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
  39. let r = location.search.substr(1).match(reg);
  40. if (r != null) return (r[2]);
  41. return null;
  42. },
  43. getValue(name) {
  44. return GM_getValue(name);
  45. },
  46. setValue(name, value) {
  47. GM_setValue(name, value);
  48. },
  49. sleep(time) {
  50. return new Promise((resolve) => setTimeout(resolve, time));
  51. },
  52. addStyle(id, tag, css) {
  53. tag = tag || 'style';
  54. let doc = document, styleDom = doc.getElementById(id);
  55. if (styleDom) return;
  56. let style = doc.createElement(tag);
  57. style.rel = 'stylesheet';
  58. style.id = id;
  59. tag === 'style' ? style.innerHTML = css : style.href = css;
  60. document.head.appendChild(style);
  61. },
  62. isHidden(el) {
  63. try {
  64. return el.offsetParent === null;
  65. } catch (e) {
  66. return false;
  67. }
  68. },
  69. query(selector) {
  70. if (Array.isArray(selector)) {
  71. let obj = null;
  72. for (let i = 0; i < selector.length; i++) {
  73. let o = document.querySelector(selector[i]);
  74. if (o) {
  75. obj = o;
  76. break;
  77. }
  78. }
  79. return obj;
  80. }
  81. return document.querySelector(selector);
  82. },
  83. getAttributesByPrefix(element, prefix) {
  84. var attributes = element.attributes;
  85. var matchingAttributes = {};
  86. for (var attribute of attributes) {
  87. var attributeName = attribute.name;
  88. var attributeValue = attribute.value;
  89.  
  90. if (attributeName.startsWith(prefix)) {
  91. matchingAttributes[attributeName] = attributeValue;
  92. }
  93. }
  94. return matchingAttributes;
  95. },
  96. openLinkInNewTab(selector) {
  97. var allLinks = document.querySelectorAll(selector);
  98.  
  99. allLinks.forEach(function (link) {
  100. link.setAttribute('target', '_blank');
  101. });
  102. }
  103. };
  104.  
  105. const opts = {
  106. post: {
  107. pathPattern: /^\/(categories\/|page|award|$)/,
  108. scrollThreshold: 200,
  109. nextPagerSelector: '.nsk-pager a.pager-next',
  110. postListSelector: 'ul.post-list',
  111. topPagerSelector: 'div.nsk-pager.pager-top',
  112. bottomPagerSelector: 'div.nsk-pager.pager-bottom',
  113. },
  114. comment: {
  115. pathPattern: /^\/post-/,
  116. scrollThreshold: 690,
  117. nextPagerSelector: '.nsk-pager a.pager-next',
  118. postListSelector: 'ul.comments',
  119. topPagerSelector: 'div.nsk-pager.post-top-pager',
  120. bottomPagerSelector: 'div.nsk-pager.post-bottom-pager',
  121. },
  122. setting: {
  123. SETTING_SIGN_IN_STATUS: 'setting_sign_in_status'
  124. }
  125. };
  126.  
  127. let main = {
  128. // 初始化配置数据
  129. initValue() {
  130. let value = [{
  131. name: opts.setting.SETTING_SIGN_IN_STATUS,
  132. value: 0
  133. }];
  134.  
  135. value.forEach((v) => {
  136. if (util.getValue(v.name) === undefined) {
  137. util.setValue(v.name, v.value);
  138. }
  139. });
  140. },
  141. loginStatus: false,
  142. //检查是否登陆
  143. checkLogin() {
  144. if (document.querySelector('#nsk-right-panel-container>.user-card')) {
  145. this.loginStatus = true;
  146. util.clog('已登录');
  147. }
  148. },
  149. // 自动签到
  150. autoSignIn(rand) {
  151. if (!this.loginStatus) return
  152.  
  153. let localTimezoneOffset = (new Date()).getTimezoneOffset();
  154. let beijingOffset = 8 * 60;
  155. let beijingTime = new Date(Date.now() + (localTimezoneOffset + beijingOffset) * 60 * 1000);
  156. let timeNow = `${beijingTime.getFullYear()}/${(beijingTime.getMonth() + 1)}/${beijingTime.getDate()}`,
  157. timeOld = util.getValue('menu_signInTime');
  158. if (!timeOld || timeOld != timeNow) { // 是新的一天
  159. util.setValue('menu_signInTime', timeNow); // 写入签到时间以供后续比较
  160.  
  161. GM_xmlhttpRequest({
  162. url: '/api/attendance?random=' + (rand || true),
  163. method: 'POST',
  164. timeout: 4000
  165. , onload: function (res) {
  166. if (res.status === 200) {
  167. let json = JSON.parse(res.responseText);
  168. if (json.success) {
  169. GM_notification({ text: '签到成功!今天午饭+' + json.gain + '个鸡腿; 积攒了' + json.current + '个鸡腿了', timeout: 3500 });
  170. }
  171. else {
  172. GM_notification({ text: '签到失败!' + json.message, timeout: 3500 });
  173. }
  174. }
  175. }, onerror: function (err) {
  176. util.clog('error');
  177. util.clog(err)
  178. }
  179. });
  180. util.clog(`[NodeSeek] 签到完成`);
  181. }
  182. },
  183. addSignTips() {
  184. let tip = document.createElement('div');
  185. tip.className = "nsplus-tip";
  186. let tip_p = document.createElement('p');
  187. tip_p.innerHTML = '今天你还没有签到哦!&emsp;【<a href="">随机抽个鸡腿<svg data-v-372de460="" class="iconpark-icon"><use data-v-372de460="" href="#chicken-leg"></use></svg></a>】&emsp;【<a href="">只要5个鸡腿</a>】&emsp;【<a href="">今天不再提示</a>】';
  188. tip.appendChild(tip_p);
  189. document.querySelector('#nsk-frame').before(tip);
  190. },
  191. quickComment() {
  192. let _this = this;
  193. document.querySelectorAll('div.comment-menu > div:nth-child(4) ').forEach(function (item) { item.onclick = function (e) { var md = document.querySelector('.md-editor'); md.style.position = 'fixed'; md.style.bottom = 0; md.style.width = '100%'; md.style.maxWidth = '720px'; md.style.zIndex = '999'; _this.addEditorCloseButton() } })
  194. },
  195. addEditorCloseButton() {
  196. var linkElement = document.createElement('a');
  197.  
  198. // 设置属性
  199. linkElement.setAttribute('data-v-f5a54ae2', '');
  200. linkElement.setAttribute('href', 'javascript:void(0)');
  201. linkElement.setAttribute('title', '关闭');
  202. linkElement.setAttribute('class', 'editor-top-button');
  203.  
  204. // 创建 <span> 元素
  205. var spanElement = document.createElement('span');
  206. spanElement.setAttribute('data-v-f5a54ae2', '');
  207. spanElement.setAttribute('class', 'i-icon i-icon-close');
  208. spanElement.innerHTML = '<svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M8 8L40 40" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8 40L40 8" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
  209.  
  210. // 将元素组合起来
  211. linkElement.appendChild(spanElement);
  212. linkElement.addEventListener("click", function (e) {
  213. var md = document.querySelector('.md-editor'); md.style.position = ""; md.style.bottom = ""; md.style.maxWidth = "";
  214. this.remove();
  215. });
  216. document.querySelector('#editor-body > div.tab-select.window_header > a[title=全屏]').after(linkElement);
  217. },
  218. //新窗口打开帖子
  219. openPostInNewTab() {
  220. util.openLinkInNewTab('.post-title>a[href]');
  221. },
  222. //自动点击跳转页链接
  223. autoJump() {
  224. if (!/^\/jump/.test(location.pathname)) return;
  225. document.querySelector('.btn').click();
  226. },
  227. blockPost(ele) {
  228. ele = ele || document;
  229. ele.querySelectorAll('.post-title>a[href]').forEach(function (item) {
  230. if (item.textContent.toLowerCase().includes("__key__")) {
  231. item.closest(".post-list-item").remove()
  232. }
  233. });
  234. },
  235. //拉黑用户
  236. blockMemberDOMInsert() {
  237. Array.from(document.querySelectorAll(".post-list .post-list-item,.content-item")).forEach((function (t, n) {
  238. var r = t.querySelector('.avatar-normal');
  239. r.addEventListener("click", (function (n) {
  240. n.preventDefault();
  241. let intervalId = setInterval(async () => {
  242. const userCard = document.querySelector('div.user-card.hover-user-card');
  243. const pmButton = document.querySelector('div.user-card.hover-user-card a.btn');
  244. if (userCard && pmButton) {
  245. clearInterval(intervalId);
  246. const dataVAttrs = util.getAttributesByPrefix(userCard, 'data-v');
  247. const userName = userCard.querySelector('a.Username').innerText;
  248. const blockBtn = document.createElement("a");
  249. for (let k in dataVAttrs) {
  250. blockBtn.setAttribute(k, dataVAttrs[k]);
  251. };
  252. blockBtn.onclick = function (e) { e.preventDefault(); main.blockMember(userName) };
  253. blockBtn.className = "btn";
  254. blockBtn.style.float = "left";
  255. blockBtn.innerText = "拉黑";
  256. pmButton.after(blockBtn);
  257. }
  258. }, 50);
  259. }))
  260. }))
  261. },
  262. // 黑名单
  263. blockMember(userName) {
  264. GM_xmlhttpRequest({
  265. url: "/api/block-list/add",
  266. method: 'POST',
  267. headers: {
  268. "Content-Type": "application/json"
  269. },
  270. data: JSON.stringify({ "block_member_name": userName }),
  271. onload: function (res) {
  272. if (res.status === 200) {
  273. let result = JSON.parse(res.responseText);
  274. if (result.success) {
  275. let msg = '屏蔽用户【' + userName + '】成功!';
  276. unsafeWindow.mscAlert(msg);
  277. util.clog(msg);
  278. } else {
  279. let msg = '屏蔽用户【' + userName + '】失败!' + result.message;
  280. unsafeWindow.mscAlert(msg);
  281. util.clog(msg);
  282. }
  283. }
  284. }, onerror: function (err) {
  285. util.clog(err);
  286. }
  287. });
  288. },
  289.  
  290. // 自动翻页
  291. autoLoading() {
  292. let opt = {};
  293. if (opts.post.pathPattern.test(location.pathname)) { opt = opts.post; }
  294. else if (opts.comment.pathPattern.test(location.pathname)) { opt = opts.comment; }
  295. else { return; }
  296. let is_requesting = false;
  297. let _this = this;
  298. this.windowScroll(function (direction, e) {
  299. if (direction === 'down') { // 下滑才准备翻页
  300. let scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
  301. if (document.documentElement.scrollHeight <= document.documentElement.clientHeight + scrollTop + opt.scrollThreshold && !is_requesting) {
  302. if (!document.querySelector(opt.nextPagerSelector)) return;
  303. let nextUrl = document.querySelector(opt.nextPagerSelector).attributes.href.value;
  304. is_requesting = true;
  305. GM_xmlhttpRequest({
  306. url: nextUrl,
  307. method: 'GET',
  308. onload: function (res) {
  309. if (res.status === 200) {
  310. let doc = new DOMParser().parseFromString(res.responseText, "text/html");
  311. _this.blockPost(doc);//过滤帖子
  312. document.querySelector(opt.postListSelector).append(...doc.querySelector(opt.postListSelector).childNodes);
  313. document.querySelector(opt.topPagerSelector).innerHTML = doc.querySelector(opt.topPagerSelector).innerHTML;
  314. document.querySelector(opt.bottomPagerSelector).innerHTML = doc.querySelector(opt.bottomPagerSelector).innerHTML;
  315. history.pushState(null, null, nextUrl);
  316. }
  317. is_requesting = false;
  318. },
  319. onerror: function (err) {
  320. is_requesting = false;
  321. util.clog(err);
  322. }
  323. });
  324. }
  325. }
  326. });
  327. },
  328. // 滚动条事件
  329. windowScroll(fn1) {
  330. var beforeScrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop,
  331. fn = fn1 || function () { };
  332. setTimeout(function () { // 延时执行,避免刚载入到页面就触发翻页事件
  333. window.addEventListener('scroll', function (e) {
  334. var afterScrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop,
  335. delta = afterScrollTop - beforeScrollTop;
  336. if (delta == 0) return false;
  337. fn(delta > 0 ? 'down' : 'up', e);
  338. beforeScrollTop = afterScrollTop;
  339. }, false);
  340. }, 1000)
  341. },
  342. switchMultiState(stateName, states) {//多态顺序切换
  343. let currState = util.getValue(stateName);
  344. currState = (currState + 1) % states.length;
  345. util.setValue(stateName, currState);
  346. this.registerMenus();
  347. },
  348. getMenuStateText(menu, stateVal) {
  349. return `${menu.states[stateVal].k} ${menu.text}(${menu.states[stateVal].v})`;
  350. },
  351. _menus: [{ name: opts.setting.SETTING_SIGN_IN_STATUS, callback: (name,states)=>main.switchMultiState(name,states), accessKey: '', text: '自动签到', states: [{ k: '❌', v: '关闭' }, { k: '🎲', v: '随机🍗' }, { k: '5️⃣', v: '5个🍗' }] }],//type:"b"--boolean;type:"m"--multi state
  352. _menuIds: [],
  353. registerMenus() {
  354. this._menuIds.forEach(function (id) {
  355. GM_unregisterMenuCommand(id);
  356. });
  357. this._menuIds = [];
  358.  
  359. const _this = this;
  360. this._menus.forEach(function (menu) {
  361. let k = menu.name;
  362. if (menu.states.length>0) {
  363. k = _this.getMenuStateText(menu, util.getValue(menu.name));
  364. }
  365. let menuId = GM_registerMenuCommand(k, function(){ menu.callback(menu.name,menu.states)});
  366. _this._menuIds.push(menuId);
  367. });
  368. },
  369. addPluginStyle() {
  370. let style = `
  371. .notie-container{ opacity: 0.8; }
  372. .nsplus-tip { background-color: rgba(255, 217, 0, 0.8); border: 0px solid black; padding: 10px; text-align: center;animation: blink 5s cubic-bezier(.68,.05,.46,.96) infinite;}
  373. /* @keyframes blink{ 0%{background-color: red;} 25%{background-color: yellow;} 50%{background-color: blue;} 75%{background-color: green;} 100%{background-color: red;} } */
  374. .nsplus-tip p,.nsplus-tip p a { color: #f00 }
  375. .nsplus-tip p a:hover {color: #0ff}
  376. `;
  377.  
  378. if (document.head) {
  379. util.addStyle('notie-style', 'style', GM_getResourceText('notieStyle'));
  380. util.addStyle('nsplus-style', 'style', style);
  381. }
  382.  
  383. const headObserver = new MutationObserver(() => {
  384. util.addStyle('notie-style', 'style', GM_getResourceText('notieStyle'));
  385. util.addStyle('nsplus-style', 'style', style);
  386. });
  387. headObserver.observe(document.head, { childList: true, subtree: true });
  388. },
  389. init() {
  390. this.initValue();
  391. this.addPluginStyle();
  392. this.checkLogin();
  393. this.autoSignIn();//自动签到
  394. this.autoJump();//自动点击跳转页
  395. this.autoLoading();//无缝加载帖子和评论
  396. this.openPostInNewTab();//在新标签页打开帖子
  397. this.blockMemberDOMInsert();//拉黑用户
  398. this.blockPost();//屏蔽帖子
  399. this.quickComment();//快捷评论
  400. util.getValue(opts.setting.SETTING_SIGN_IN_STATUS) === 0 && this.addSignTips();//签到提示
  401. this.registerMenus();
  402. const css = GM_getResourceText("highlightStyle");
  403. GM_addStyle(css);
  404. GM_addElement('script', {
  405. src: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js'
  406. });
  407. GM_addElement('script', {
  408. textContent: 'window.onload = function(){hljs.highlightAll();}'
  409. });
  410. }
  411. }
  412. main.init();
  413. })();