您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tracks YouTube session time, video watch time, daily/monthly stats, with export/import support.
当前为
// ==UserScript== // @name YouTube Watch Tracker v1.2 // @namespace http://tampermonkey.net/ // @version 1.2 // @description Tracks YouTube session time, video watch time, daily/monthly stats, with export/import support. // @author Void // @match *://*.youtube.com/* // @grant none // @license CC-BY-ND-4.0 // ==/UserScript== (function () { 'use strict'; const STORAGE_KEY = 'yt_watch_tracker_data'; let data = {}; let sessionTime = 0; let videoTime = 0; let videoTimer = null; let sessionTimer = null; let currentDate = new Date().toISOString().slice(0, 10); let overlay; function load() { try { const stored = localStorage.getItem(STORAGE_KEY); if (stored) data = JSON.parse(stored); } catch { data = {}; } } function save() { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } function ensureDateStats() { if (!data[currentDate]) { data[currentDate] = { watched: 0, wasted: 0, session: 0 }; } } function fmtTime(t) { const h = Math.floor(t / 3600); const m = Math.floor((t % 3600) / 60); const s = t % 60; return `${h}h ${String(m).padStart(2, '0')}m ${String(s).padStart(2, '0')}s`; } function updateOverlay() { ensureDateStats(); const daily = data[currentDate]; const month = currentDate.slice(0, 7); let monthly = { watched: 0, wasted: 0, session: 0 }; for (const [date, stats] of Object.entries(data)) { if (date.startsWith(month)) { monthly.watched += stats.watched; monthly.wasted += stats.wasted; monthly.session += stats.session; } } overlay.querySelector('.ytwt-text').textContent = `📅 Today: ${daily.watched} videos | 🕒 Wasted: ${fmtTime(daily.wasted)} | ⌛ Session: ${fmtTime(sessionTime)}\n` + `📆 This Month: ${monthly.watched} vids | 🕒 Wasted: ${fmtTime(monthly.wasted)}`; } function createOverlay() { overlay = document.createElement('div'); overlay.style = ` position: fixed; bottom: 10px; left: 10px; background: rgba(0,0,0,0.85); color: #fff; padding: 8px; border-radius: 8px; font-size: 13px; font-family: 'Segoe UI'; font-weight: 600; z-index: 99999; white-space: pre; pointer-events: auto; box-shadow: 0 0 8px rgba(0,0,0,0.7); user-select: none; `; const text = document.createElement('div'); text.className = 'ytwt-text'; overlay.appendChild(text); const exportBtn = document.createElement('button'); exportBtn.textContent = 'Export'; exportBtn.style = btnStyle(); exportBtn.onclick = () => { navigator.clipboard.writeText(JSON.stringify(data, null, 2)); alert('Data copied to clipboard.'); }; const importBtn = document.createElement('button'); importBtn.textContent = 'Import'; importBtn.style = btnStyle(); importBtn.onclick = () => { const json = prompt('Paste data to import:'); try { const parsed = JSON.parse(json); if (typeof parsed === 'object') { data = parsed; save(); updateOverlay(); alert('Import successful.'); } else throw 0; } catch { alert('Invalid JSON.'); } }; overlay.appendChild(exportBtn); overlay.appendChild(importBtn); document.body.appendChild(overlay); } function btnStyle() { return ` margin-left: 6px; background: #333; color: #eee; border: 1px solid #555; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; `; } function observeVideo() { new MutationObserver(() => { const video = document.querySelector('video'); if (!video || video === window.__lastYTVideo) return; window.__lastYTVideo = video; ensureDateStats(); data[currentDate].watched++; save(); updateOverlay(); clearInterval(videoTimer); videoTimer = setInterval(() => { if (video.readyState >= 2 && !video.paused && !video.ended) { data[currentDate].wasted++; videoTime++; if (videoTime % 5 === 0) save(); updateOverlay(); } }, 1000); }).observe(document.body, { childList: true, subtree: true }); } function startSessionTimer() { sessionTimer = setInterval(() => { sessionTime++; ensureDateStats(); data[currentDate].session++; if (sessionTime % 10 === 0) save(); updateOverlay(); }, 1000); } function init() { load(); ensureDateStats(); createOverlay(); observeVideo(); startSessionTimer(); } window.addEventListener('load', () => setTimeout(init, 500)); })();