您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Simple userscript to gain insights into your reading speed on ttsu
// ==UserScript== // @name Reading speed statistics - ttsu.app // @namespace Violentmonkey Scripts // @match https://reader.ttsu.app/b* // @grant GM_addElement // @version 0.1 // @author GrumpyThomas // @license MIT // @description Simple userscript to gain insights into your reading speed on ttsu // ==/UserScript== (function (addElement) { const oneMinuteInMs = 60 * 1000; let bodyElement = document.querySelector('body'); let readSpeedElement = null; let readInfo = { id: null, startCharacterCount: null, startTime: null, mostRecentCharCount: null, totalCharsRead: null, }; let bodyObserver = new MutationObserver(function () { let node = getProgressStatisticElement(); if (!node) { return; } if (!readSpeedElement || !node.contains(readSpeedElement)) { readSpeedElement = addElement(node, 'div'); } let currentCharCount = parseInt(node.innerText.split('/').shift().trim()); if (currentCharCount === 0) { return; } // initialize const newTimestamp = Date.now(); const id = window.location.href; if (readInfo.startCharacterCount === null || readInfo.startTime === null || readInfo.id !== id) { logInformation('Initialize readInfo'); readInfo.id = id; readInfo.startCharacterCount = currentCharCount; readInfo.startTime = Date.now(); readInfo.mostRecentCharCount = currentCharCount; readInfo.totalCharsRead = 0; displayReadSpeed(0, 0); return; } if (readInfo.mostRecentCharCount === currentCharCount) { return; } let readCharacters = currentCharCount - readInfo.startCharacterCount; let readDurationInMs = Date.now() - readInfo.startTime; displayReadSpeed(readDurationInMs, readCharacters); readInfo.mostRecentCharCount = currentCharCount; }); /* * start observing on the body and it's child elements * characterData makes sure the observer detects text changes * which is required to detect progress changes */ bodyObserver.observe(bodyElement, { subtree: true, childList: true, characterData: true }); function displayReadSpeed(durationInMs, readCharacters) { let readDuration = durationInMs / oneMinuteInMs / 60; let charsPerHour = Math.floor(readCharacters / readDuration) || 0; readSpeedElement.innerText = `${msToTime(durationInMs)} (${charsPerHour}/hr) ${readCharacters}`; } })( (node, tag) => { // make the script available with and without userscript extension if (window.GM_addElement) { logInformation('Using GM_addElement'); return GM_addElement(node, tag); } logInformation('Using document.createElement'); let createdElement = document.createElement(tag); node.appendChild(createdElement); return createdElement; } ); function msToTime(duration) { let seconds = Math.floor((duration / 1000) % 60); let minutes = Math.floor((duration / (1000 * 60)) % 60); let hours = Math.floor((duration / (1000 * 60 * 60)) % 24); return `${padNumber(hours, 2)}:${padNumber(minutes, 2)}:${padNumber(seconds, 2)}`; } function padNumber(number, length) { return number.toString().padStart(length, '0'); } function getProgressStatisticElement() { let node = null; // try find the progress statistic element based on a custom data attribute node = document.querySelector('div[data-target="progress"]'); if (node) { return node; } // fall back on xpath let divs = document.evaluate("//div[contains(., '%')]", document, null, XPathResult.ANY_TYPE, null); while (node = divs.iterateNext()) { if (Array.from(node.children).length === 0 && node.textContent?.length > 0) { node.dataset.target = "progress"; return node; } } } function logInformation(message) { if (console && console.log) { console.log(message); } }