您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically skip sponsors in YouTube videos using SponsorBlock API
// ==UserScript== // @name YouTube SponsorBlock // @description Automatically skip sponsors in YouTube videos using SponsorBlock API // @namespace http://tampermonkey.net/ // @icon https://cdn-icons-png.flaticon.com/64/2504/2504965.png // @version 0.0.3 // @author rxm // @match https://www.youtube.com/* // @grant GM_xmlhttpRequest // @connect api.sponsor.ajay.app // @license MIT // ==/UserScript== (function() { 'use strict'; const SB_API = 'https://api.sponsor.ajay.app/api/skipSegments'; const sbDataCache = new Map(); let activeVideoId = null; let listenerAttached = false; // Only skip *real sponsor ads* (not intros, outros, selfpromo, etc.) const skipCategories = ['sponsor']; function log(...args) { console.debug('[SponsorBlock]', ...args); } function getVideoId() { try { const u = new URL(location.href); if (u.searchParams.get('v')) return u.searchParams.get('v'); const shorts = location.pathname.match(/\/shorts\/([^/?#]+)/); if (shorts) return shorts[1]; const meta = document.querySelector('meta[itemprop="videoId"]'); if (meta) return meta.getAttribute('content'); } catch {} return null; } function getVideoElement() { return document.querySelector('video.html5-main-video'); } function fetchSponsorSegments(videoId, callback, attempt = 1) { if (sbDataCache.has(videoId)) { callback(sbDataCache.get(videoId)); return; } const url = `${SB_API}?videoID=${encodeURIComponent(videoId)}&categories=${encodeURIComponent(JSON.stringify(skipCategories))}`; GM_xmlhttpRequest({ method: 'GET', url, headers: { 'Accept': 'application/json' }, onload: (res) => { try { const data = JSON.parse(res.responseText); const segments = (Array.isArray(data) ? data : []).map(item => { const seg = item.segment || item.segments || []; const cat = item.category || (item.categories && item.categories[0]) || 'sponsor'; const start = Number(seg[0]) || 0; const end = Number(seg[1]) || 0; return end > start ? { start, end, category: cat } : null; }).filter(Boolean).sort((a, b) => a.start - b.start); // Merge overlapping segments const merged = []; for (const s of segments) { const last = merged[merged.length - 1]; if (!last || s.start > last.end) { merged.push({ ...s }); } else { last.end = Math.max(last.end, s.end); } } sbDataCache.set(videoId, merged); callback(merged); } catch (e) { if (attempt < 2) { log('Retrying fetch due to parse error…'); return fetchSponsorSegments(videoId, callback, attempt + 1); } log('Failed to parse API response', e); callback([]); } }, onerror: () => { if (attempt < 2) { log('Retrying fetch due to network error…'); fetchSponsorSegments(videoId, callback, attempt + 1); } else { callback([]); } }, ontimeout: () => callback([]) }); } function attachSkipper(videoId) { const vid = getVideoElement(); if (!vid || !videoId) return; if (activeVideoId === videoId && listenerAttached) return; fetchSponsorSegments(videoId, (segments) => { if (!segments.length) return; activeVideoId = videoId; let nextSegIndex = 0; const onTimeUpdate = () => { let t = vid.currentTime || 0; while (nextSegIndex < segments.length) { const seg = segments[nextSegIndex]; if (seg && t >= seg.start && t < seg.end) { t = vid.currentTime = +(seg.end + 0.35).toFixed(2); // skip forward safely log(`Skipped ${seg.category}: ${seg.start} → ${seg.end}`); nextSegIndex++; } else { break; } } }; const onSeeking = () => { // Recalculate based on current position nextSegIndex = segments.findIndex(s => vid.currentTime < s.end); if (nextSegIndex < 0) nextSegIndex = segments.length; // If you land inside a sponsor block, skip it immediately const inside = segments.find(s => vid.currentTime >= s.start && vid.currentTime < s.end); if (inside) { vid.currentTime = +(inside.end + 0.35).toFixed(2); log(`Seek landed inside sponsor, skipped ${inside.category}`); nextSegIndex = segments.indexOf(inside) + 1; } }; vid.addEventListener('timeupdate', onTimeUpdate); vid.addEventListener('seeking', onSeeking); listenerAttached = true; // Watch if the video element gets replaced const checkInterval = setInterval(() => { if (!document.contains(vid) || getVideoElement() !== vid) { try { vid.removeEventListener('timeupdate', onTimeUpdate); vid.removeEventListener('seeking', onSeeking); } catch {} clearInterval(checkInterval); listenerAttached = false; activeVideoId = null; } }, 1500); }); } // Monitor for video changes function observePage() { let lastVideoId = null; setInterval(() => { const videoId = getVideoId(); if (videoId && videoId !== lastVideoId) { lastVideoId = videoId; attachSkipper(videoId); } }, 2000); } observePage(); })();