您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Records webpage study time and displays a floating timer window in the upper right corner, with exportable study logs
// ==UserScript== // @name Study Timer // @namespace YMHOMER // @version 2.0 // @description Records webpage study time and displays a floating timer window in the upper right corner, with exportable study logs // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== (function() { 'use strict'; // Retrieve the base URL (main page) for logging consistency across multiple tabs of the same site const baseUrl = location.origin; let timerDisplayed = GM_getValue(baseUrl + "_timerDisplayed", false); let timerActive = GM_getValue(baseUrl + "_timerActive", false); let startTime = Date.now(); let elapsed = 0; let pauseCount = 0; let totalPauseTime = 0; let interval; let focus = document.hasFocus(); let pauseStartTime = null; // Reset on load to avoid interference from previous records GM_setValue(baseUrl + "_startTime", startTime); GM_setValue(baseUrl + "_elapsed", elapsed); GM_setValue(baseUrl + "_pauseCount", pauseCount); GM_setValue(baseUrl + "_totalPauseTime", totalPauseTime); // Initialize the timer window const timerWindow = document.createElement('div'); timerWindow.style.position = 'fixed'; timerWindow.style.top = '10px'; timerWindow.style.right = '10px'; timerWindow.style.padding = '10px'; timerWindow.style.zIndex = '9999'; timerWindow.style.background = 'rgba(0, 0, 0, 0.8)'; timerWindow.style.color = '#fff'; timerWindow.style.borderRadius = '5px'; timerWindow.style.cursor = 'move'; timerWindow.style.display = timerDisplayed ? 'block' : 'none'; timerWindow.innerHTML = `<span id="timeDisplay">00:00:00</span> <button id="toggleTimer" style="margin-left: 5px;">${timerActive ? 'Pause' : 'Start'}</button> <button id="stopTimer" style="margin-left: 5px;">End</button>`; document.body.appendChild(timerWindow); // Enable dragging of the timer window timerWindow.onmousedown = function(e) { let shiftX = e.clientX - timerWindow.getBoundingClientRect().left; let shiftY = e.clientY - timerWindow.getBoundingClientRect().top; document.onmousemove = function(e) { timerWindow.style.left = e.pageX - shiftX + 'px'; timerWindow.style.top = e.pageY - shiftY + 'px'; }; document.onmouseup = function() { document.onmousemove = null; timerWindow.onmouseup = null; }; }; timerWindow.ondragstart = () => false; // Toggle timer state (start/pause) function toggleTimer() { if (timerActive) { // Pause the timer clearInterval(interval); pauseCount++; pauseStartTime = Date.now(); elapsed += Date.now() - startTime; GM_setValue(baseUrl + "_elapsed", elapsed); timerWindow.style.background = 'red'; document.getElementById('toggleTimer').innerText = 'Resume'; } else { // Resume the timer if (pauseStartTime) { totalPauseTime += Date.now() - pauseStartTime; pauseStartTime = null; } startTime = Date.now(); timerWindow.style.background = 'rgba(0, 0, 0, 0.8)'; interval = setInterval(updateTime, 1000); document.getElementById('toggleTimer').innerText = 'Pause'; } timerActive = !timerActive; GM_setValue(baseUrl + "_timerActive", timerActive); GM_setValue(baseUrl + "_pauseCount", pauseCount); GM_setValue(baseUrl + "_totalPauseTime", totalPauseTime); } // Update timer display function updateTime() { let totalTime = elapsed + (Date.now() - startTime); document.getElementById('timeDisplay').innerText = new Date(totalTime).toISOString().substr(11, 8); } // Stop timer, display dialog with record details, and reset timer to zero function stopTimer() { clearInterval(interval); let currentSessionTime = Date.now() - startTime; if (timerActive) elapsed += currentSessionTime; // If paused, add the final pause duration if (pauseStartTime) { totalPauseTime += Date.now() - pauseStartTime; pauseStartTime = null; } let endTime = Date.now(); let totalElapsed = GM_getValue("total_elapsed", 0) + elapsed; GM_setValue("total_elapsed", totalElapsed); // Log session by date const sessionDate = new Date().toISOString().split("T")[0]; const history = GM_getValue(baseUrl + "_history", {}); if (!history[sessionDate]) { history[sessionDate] = { sessions: [] }; } history[sessionDate].sessions.push({ start: new Date(startTime).toLocaleString(), end: new Date(endTime).toLocaleString(), studyTime: new Date(currentSessionTime).toISOString().substr(11, 8), pauseCount, totalPauseTime: new Date(totalPauseTime).toISOString().substr(11, 8) }); GM_setValue(baseUrl + "_history", history); // Prepare the report with additional details const report = `URL: ${baseUrl}\n` + `Start Time: ${new Date(startTime).toLocaleString()}\n` + `End Time: ${new Date(endTime).toLocaleString()}\n` + `Session Study Time: ${new Date(currentSessionTime).toISOString().substr(11, 8)}\n` + `Total Study Time: ${new Date(totalElapsed).toISOString().substr(11, 8)}\n` + `Total Pause Time: ${new Date(totalPauseTime).toISOString().substr(11, 8)}\n` + `Pause Count: ${pauseCount}`; // Show the dialog with record details showEndDialog(report); // Reset timer display to zero and reset variables timerActive = false; elapsed = 0; pauseCount = 0; totalPauseTime = 0; document.getElementById('timeDisplay').innerText = "00:00:00"; document.getElementById('toggleTimer').innerText = 'Start'; timerWindow.style.background = 'rgba(0, 0, 0, 0.8)'; } // Show end dialog with record details function showEndDialog(report) { const dialog = document.createElement('dialog'); dialog.style.width = '80%'; dialog.style.maxWidth = '500px'; dialog.style.padding = '20px'; dialog.style.border = '2px solid #888'; dialog.style.borderRadius = '8px'; dialog.style.backgroundColor = '#f9f9f9'; dialog.style.boxShadow = '0px 4px 8px rgba(0, 0, 0, 0.2)'; let content = `<h2 style="text-align:center; color: #333;">Session Summary</h2><pre style="white-space: pre-wrap; font-size:0.9em; color: #333; padding:10px;">${report}</pre>`; content += `<div style="text-align:center; margin-top: 15px;"> <button id="exportRecord" style="margin-right:10px; padding:8px 16px; background-color:#333; color:#fff; border:none; border-radius:4px; cursor:pointer;">Export Record</button> <button id="closeDialog" style="padding:8px 16px; background-color:#333; color:#fff; border:none; border-radius:4px; cursor:pointer;">Close</button> </div>`; dialog.innerHTML = content; document.body.appendChild(dialog); dialog.showModal(); // Export button functionality document.getElementById('exportRecord').onclick = () => saveRecord(report); // Close button functionality document.getElementById('closeDialog').onclick = () => dialog.close(); dialog.addEventListener('close', () => document.body.removeChild(dialog)); } // Save and export the study record function saveRecord(data) { const blob = new Blob([data], { type: 'text/plain' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `study_record_${new Date().toISOString().slice(0, 10)}.txt`; link.click(); } // Check if window is in focus function focusCheck() { focus = document.hasFocus(); if (focus && timerActive) { startTime = Date.now(); interval = setInterval(updateTime, 1000); } else if (!focus && timerActive) { clearInterval(interval); pauseCount++; pauseStartTime = Date.now(); elapsed += Date.now() - startTime; } } // View study history with a styled calendar dialog function viewHistory() { const history = GM_getValue(baseUrl + "_history", {}); const dialog = document.createElement('dialog'); dialog.style.width = '90%'; dialog.style.maxWidth = '600px'; dialog.style.padding = '20px'; dialog.style.border = '2px solid #888'; dialog.style.borderRadius = '8px'; dialog.style.backgroundColor = '#f9f9f9'; dialog.style.boxShadow = '0px 4px 8px rgba(0, 0, 0, 0.2)'; let content = `<h2 style="text-align:center; color: #333;">Study History for ${baseUrl}</h2><table border="0" style="width:100%; font-size:0.9em; color: #333;">`; for (const date in history) { const daySessions = history[date].sessions; content += `<tr style="background-color:#eee; color:#555;"><td colspan="3" style="padding: 8px;"><strong>${date}</strong></td></tr>`; daySessions.forEach((session, index) => { content += `<tr><td style="padding: 6px;">Session ${index + 1}</td><td>Start: ${session.start}</td><td>End: ${session.end}</td></tr>`; content += `<tr><td colspan="3" style="padding: 6px 8px;">Study Time: ${session.studyTime}, Pauses: ${session.pauseCount}, Total Pause Time: ${session.totalPauseTime}</td></tr>`; }); } content += `</table><br/><button id="closeDialog" style="margin-top: 15px; padding: 8px 16px; background-color: #333; color: #fff; border: none; border-radius: 4px; cursor: pointer;">Close</button>`; dialog.innerHTML = content; document.body.appendChild(dialog); dialog.showModal(); document.getElementById('closeDialog').onclick = () => dialog.close(); dialog.addEventListener('close', () => document.body.removeChild(dialog)); } // Bind events document.getElementById('toggleTimer').addEventListener('click', toggleTimer); document.getElementById('stopTimer').addEventListener('click', stopTimer); window.addEventListener('focus', focusCheck); window.addEventListener('blur', focusCheck); // Add Tampermonkey menu options GM_registerMenuCommand("Toggle Study Timer Display", function() { timerDisplayed = !timerDisplayed; GM_setValue(baseUrl + "_timerDisplayed", timerDisplayed); timerWindow.style.display = timerDisplayed ? 'block' : 'none'; }); GM_registerMenuCommand("View Study History", viewHistory); // Initialize if (timerActive) { interval = setInterval(updateTime, 1000); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址