您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
You no longer need to click to know whether a video on submeta.io has been commented, as the comment count is displayed right on the Discuss button
// ==UserScript== // @name Show comment number on Discuss button // @namespace http://tampermonkey.net/ // @version 1.0 // @description You no longer need to click to know whether a video on submeta.io has been commented, as the comment count is displayed right on the Discuss button // @match https://submeta.io/* // @icon https://www.google.com/s2/favicons?sz=64&domain=submeta.io // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; function log(obj){ return console.log(obj); } class Mutex { constructor() { this.isLocked = false; this.waitingResolvers = []; } async lock() { log('locking mutex') if (this.isLocked) { // wait until released log('mutex already locked, store job in queue...') await new Promise(resolve => this.waitingResolvers.push(resolve)); } this.isLocked = true; } unlock() { log('unlocking mutex') this.isLocked = false; this.waitingResolvers.forEach(resolve => resolve()); this.waitingResolvers = []; } discardQueue() { log('discarding mutex queue') this.waitingResolvers = []; } async runExclusive(fn) { await this.lock(); try { return await fn(); } finally { this.unlock(); } } } //let sleepDuration = 2000; const mutex = new Mutex(); async function launch() { await mutex.runExclusive(async () => { const a = document.querySelector('a[href^="/discussion"]'); if (!a || !a.href) { log('Link not found on ' + a); return } // optional: only handle same-origin links const url = new URL(a.href, location.href); if (url.origin !== location.origin) { log(url.origin + ' !== ' + location.origin) return }; // const dur = sleepDuration // sleepDuration += 2000 // log('Sleeping ' + dur +'ms') // await new Promise(r => setTimeout(r, dur)); // log('Done sleeping ' + dur +'ms') let discussSpans = Array.from(a.querySelectorAll('span')).filter(it=>it.textContent === 'Discuss') if (discussSpans.length === 0) { log('Discuss button does not exist') return } // fetch the target page (include credentials for cookies) log('Fetching Discuss link: ' + a.href); const res = await fetch(url.href, { credentials: 'include' }); if (!res.ok) throw new Error(`Fetch failed: ${res.status}`); const html = await res.text(); // parse HTML into a Document const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const scriptBlock = doc.querySelector('script#__NEXT_DATA__') const data = JSON.parse(scriptBlock.textContent) const commentCount = countComments(data.props.pageProps.discussion) discussSpans = Array.from(a.querySelectorAll('span')).filter(it=>it.textContent === 'Discuss') if (discussSpans.length === 0) { log('Discuss button does not exist') return } for (const elt of discussSpans){ elt.textContent += ' ('+commentCount+')' log('Renaming to ' + elt.textContent) //sleepDuration = 2000 } mutex.discardQueue() }); } function countComments(comments) { if (!comments || comments.length === 0) return 0; return comments.reduce((total, comment) => { return total + 1 + countComments(comment.replies); }, 0); } // example handler - replace with your logic function handleFetchedDoc(doc, href, linkElement) { // e.g. find a node, extract metadata, etc. const title = doc.querySelector('title')?.textContent || '(no title)'; const someNode = doc.querySelector('.some-class'); console.log('Inspected', href, { title, someNodeExists: !!someNode }); // If you want to show the result in-page instead of console: // showPreviewModal(title, doc.querySelector('#main')?.innerHTML || '(no main)'); } window.addEventListener('locationchange', () => { console.log('SPA navigation detected. New URL:', location.href); }); // Initial run launch(); // Observe dynamically added inputs const observer = new MutationObserver(() => launch()); observer.observe(document.body, { childList: true, subtree: true }); })();