您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhanced Twitch UI: timers, ad muting, filters, dropdown settings, QoL features with fixed dropdown behavior
// ==UserScript== // @name Stream Watch Timer Plus v8.1 // @namespace http://tampermonkey.net/ // @version 8.1 // @description Enhanced Twitch UI: timers, ad muting, filters, dropdown settings, QoL features with fixed dropdown behavior // @author Void // @match https://www.twitch.tv/* // @grant none // @license All rights reserved // ==/UserScript== (function(){ 'use strict'; let sessionSec = 0, totalSec = 0, timer = null; const channel = window.location.pathname.split('/')[1]?.toLowerCase() || 'unknown'; let dropsEnabled = false; let timerEl, resetBtn, settingsBtn, dropdownBtn, dropdownList, dropdownOptions = []; let dropdownOpen = false; const posMap = { 'TL':'top-left','TM':'top-middle','TR':'top-right', 'BL':'bottom-left','BM':'bottom-middle','BR':'bottom-right' }; const posStyles = { 'top-left': {top:'10px', left:'10px'}, 'top-middle': {top:'10px', left:'50%', transform:'translateX(-50%)'}, 'top-right': {top:'10px', right:'10px'}, 'bottom-left': {bottom:'10px', left:'10px'}, 'bottom-middle': {bottom:'10px', left:'50%', transform:'translateX(-50%)'}, 'bottom-right': {bottom:'10px', right:'10px'} }; function key(suf) { return `swt_${channel}_${suf}`; } function loadTotal() { return parseInt(localStorage.getItem(key('tot')) || '0', 10); } function saveTotal() { localStorage.setItem(key('tot'), totalSec); } function loadPos() { const pos = localStorage.getItem(key('pos')); return (pos && posMap[pos]) ? pos : 'BR'; } function savePos(val) { if(val && posMap[val]) localStorage.setItem(key('pos'), val); } function fmt(ts) { const h = Math.floor(ts / 3600), m = Math.floor((ts % 3600) / 60), s = ts % 60; return h ? `${h}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}` : `${m}:${String(s).padStart(2,'0')}`; } function detectDrops() { dropsEnabled = Array.from(document.querySelectorAll('[data-a-target="stream-tag"]')) .some(el => /drops enabled/i.test(el.textContent)); } function autoClaim() { const btn = document.querySelector('[data-a-target="claim-drop-button"]'); if(btn) btn.click(); } function muteAds(v) { const selectors = ['[data-test-selector="ad-banner"]','.player-ad-overlay','[class*="ad-label"]','.sponsored-pill']; v.muted = selectors.some(sel => document.querySelector(sel)); } function removeBanners() { document.querySelectorAll('[data-test-selector="ad-banner"],.top-nav__prime,.video-ad__container,.player-ad-overlay').forEach(e=>e.remove()); } let lastSidebarFilter = 0; function filterSidebar() { const now = performance.now(); if(now - lastSidebarFilter < 1500) return; lastSidebarFilter = now; document.querySelectorAll('a[data-a-target="side-nav-card"]').forEach(card => { const vt = card.querySelector('[data-a-target="side-nav-viewer-count"]')?.textContent?.toLowerCase(); if(!vt) return; let count; if(vt.includes('k')) { const v = vt.replace(/,/g,'.').match(/[\d.]+/); count = v ? parseFloat(v[0]) * 1000 : 0; } else { const v = vt.match(/\d+/); count = v ? parseInt(v[0],10) : 0; } card.style.display = count > 20 ? 'none' : ''; }); } function autoTheatre() { const btn = document.querySelector('[data-a-target="player-theatre-mode-button"]'); if(btn && !document.body.classList.contains('theatre-mode')) btn.click(); } function showFollowerAge() { const fBtnParent = document.querySelector('[data-a-target="follow-button"]')?.parentElement; if(!fBtnParent || fBtnParent.querySelector('.fa-age')) return; fetch(`https://api.ivr.fi/v2/twitch/user/followage/${channel}`) .then(res => res.json()) .then(data => { if(data && data.followageHuman){ const span = document.createElement('span'); span.className = 'fa-age'; span.textContent = ` | Following for: ${data.followageHuman}`; span.style.fontSize = '12px'; span.style.color = '#ccc'; fBtnParent.appendChild(span); } }).catch(() => {}); } function updateTimerText() { if(!timerEl) return; let txt = `⏱ Total: ${fmt(totalSec)} | Session: ${fmt(sessionSec)}`; if(dropsEnabled) { const remain = Math.max(0, 900 - sessionSec); txt += ` | 🎁 Drop in ${Math.ceil(remain / 60)}m`; } const span = timerEl.querySelector('.swt-text'); if(span) span.textContent = txt; } function applyPosition(code) { if(!timerEl) return; const style = posStyles[posMap[code]]; timerEl.style.top = timerEl.style.bottom = timerEl.style.left = timerEl.style.right = timerEl.style.transform = ''; Object.assign(timerEl.style, style); } function toggleDropdown(forceOpen) { dropdownOpen = forceOpen !== undefined ? forceOpen : !dropdownOpen; dropdownList.style.display = dropdownOpen ? 'block' : 'none'; dropdownBtn.setAttribute('aria-expanded', dropdownOpen); if(dropdownOpen){ // Highlight selected option const currentVal = loadPos(); dropdownOptions.forEach(opt => opt.classList.toggle('selected', opt.dataset.value === currentVal)); dropdownList.focus(); } } function dropdownKeyHandler(e) { if(!dropdownOpen) return; const selected = dropdownList.querySelector('.selected'); if(e.key === 'ArrowDown' || e.key === 'Tab'){ e.preventDefault(); let next = selected?.nextElementSibling || dropdownList.firstElementChild; if(selected) selected.classList.remove('selected'); next.classList.add('selected'); next.focus(); } else if(e.key === 'ArrowUp'){ e.preventDefault(); let prev = selected?.previousElementSibling || dropdownList.lastElementChild; if(selected) selected.classList.remove('selected'); prev.classList.add('selected'); prev.focus(); } else if(e.key === 'Enter'){ e.preventDefault(); if(selected){ const val = selected.dataset.value; savePos(val); applyPosition(val); dropdownBtn.textContent = val; toggleDropdown(false); dropdownBtn.focus(); } } else if(e.key === 'Escape'){ e.preventDefault(); toggleDropdown(false); dropdownBtn.focus(); } } function createDropdown() { const container = document.createElement('div'); container.style.position = 'relative'; container.style.display = 'inline-block'; container.style.marginLeft = '6px'; dropdownBtn = document.createElement('button'); dropdownBtn.type = 'button'; dropdownBtn.textContent = loadPos(); dropdownBtn.setAttribute('aria-haspopup', 'listbox'); dropdownBtn.setAttribute('aria-expanded', 'false'); Object.assign(dropdownBtn.style, { background: '#222', color: '#eee', border: '1px solid #555', borderRadius: '4px', padding: '3px 8px', cursor: 'pointer', fontSize: '12px', minWidth: '50px', userSelect: 'none' }); dropdownBtn.addEventListener('click', e => { e.stopPropagation(); toggleDropdown(); }); dropdownBtn.addEventListener('keydown', e => { if(['ArrowDown','ArrowUp','Enter',' '].includes(e.key)){ e.preventDefault(); toggleDropdown(true); dropdownList.focus(); } }); dropdownList = document.createElement('ul'); dropdownList.tabIndex = -1; dropdownList.setAttribute('role', 'listbox'); Object.assign(dropdownList.style, { position: 'absolute', top: '100%', left: '0', background: '#222', border: '1px solid #555', borderRadius: '4px', marginTop: '2px', padding: '2px 0', listStyle: 'none', width: '60px', maxHeight: '160px', overflowY: 'auto', boxShadow: '0 2px 6px rgba(0,0,0,0.5)', display: 'none', zIndex: '10000', userSelect: 'none' }); dropdownList.addEventListener('keydown', dropdownKeyHandler); dropdownList.addEventListener('click', e => { if(e.target.tagName === 'LI'){ const val = e.target.dataset.value; if(val){ savePos(val); applyPosition(val); dropdownBtn.textContent = val; toggleDropdown(false); dropdownBtn.focus(); } } }); // Prevent dropdown from closing on clicks inside dropdownList.addEventListener('mousedown', e => e.preventDefault()); dropdownOptions = []; Object.entries(posMap).forEach(([code]) => { const li = document.createElement('li'); li.textContent = code; li.dataset.value = code; li.setAttribute('role', 'option'); li.tabIndex = -1; Object.assign(li.style, { padding: '4px 10px', cursor: 'pointer', color: '#eee', fontSize: '12px', userSelect: 'none' }); li.addEventListener('mouseenter', () => { dropdownOptions.forEach(o => o.classList.remove('selected')); li.classList.add('selected'); li.focus(); }); li.addEventListener('mouseleave', () => { li.classList.remove('selected'); }); dropdownList.appendChild(li); dropdownOptions.push(li); }); container.appendChild(dropdownBtn); container.appendChild(dropdownList); return container; } // Close dropdown if clicking outside document.addEventListener('click', () => { if(dropdownOpen) toggleDropdown(false); }); function createOverlay() { timerEl = document.createElement('div'); timerEl.id = 'watch-time-overlay'; Object.assign(timerEl.style, { position:'absolute', background:'rgba(0,0,0,0.85)', color:'#fff', padding:'8px', borderRadius:'8px', fontSize:'14px', fontFamily:'Segoe UI', fontWeight:'600', zIndex:'9999', pointerEvents:'auto', display:'flex', alignItems:'center', gap:'8px', whiteSpace:'nowrap', textShadow:'0 0 4px #000', userSelect: 'none', minWidth: '220px', boxShadow: '0 0 8px rgba(0,0,0,0.7)' }); const span = document.createElement('span'); span.className = 'swt-text'; span.style.flex = '1'; timerEl.appendChild(span); resetBtn = document.createElement('button'); resetBtn.textContent = 'Reset'; Object.assign(resetBtn.style,{ cursor:'pointer', marginLeft:'4px', fontSize:'12px', background:'#333', color:'#eee', border:'1px solid #555', borderRadius:'4px', padding:'2px 8px', userSelect:'none' }); resetBtn.onmouseenter = () => resetBtn.style.background = '#444'; resetBtn.onmouseleave = () => resetBtn.style.background = '#333'; resetBtn.onclick = () => { if(confirm('Reset total and session time?')){ totalSec = sessionSec = 0; saveTotal(); updateTimerText(); } }; timerEl.appendChild(resetBtn); settingsBtn = document.createElement('button'); settingsBtn.textContent = '⚙️'; Object.assign(settingsBtn.style,{ cursor:'pointer', fontSize:'16px', marginLeft:'4px', background:'transparent', border:'none', color:'#eee', userSelect:'none' }); settingsBtn.onmouseenter = () => settingsBtn.style.color = '#ddd'; settingsBtn.onmouseleave = () => settingsBtn.style.color = '#eee'; settingsBtn.onclick = e => { e.stopPropagation(); toggleDropdown(); }; timerEl.appendChild(settingsBtn); const dropdownContainer = createDropdown(); dropdownContainer.style.marginLeft = '8px'; timerEl.appendChild(dropdownContainer); applyPosition(loadPos()); return timerEl; } function startUI(video, container){ if(timer) return; totalSec = loadTotal(); sessionSec = 0; detectDrops(); container.appendChild(createOverlay()); updateTimerText(); autoTheatre(); showFollowerAge(); timer = setInterval(() => { if(video.readyState >= 2 && !video.paused && !video.ended){ totalSec++; sessionSec++; updateTimerText(); if(totalSec % 5 === 0) saveTotal(); } autoClaim(); muteAds(video); removeBanners(); }, 1000); setInterval(filterSidebar, 1500); } function initWatch(){ new MutationObserver(() => { const v = document.querySelector('video'); const c = document.querySelector('.video-player__container, .video-player__overlay'); const o = document.getElementById('watch-time-overlay'); if(v && c && !o){ startUI(v,c); } }).observe(document.body, {childList:true, subtree:true}); } window.addEventListener('load', () => setTimeout(initWatch, 1000)); })();