// ==UserScript== // @name YouTube 自動スキップスクリプト // @namespace http://tampermonkey.net/ // @version 2.0 // @description 指定の動画で休憩パートを自動スキップする // @author れいけん // @match https://www.youtube.com/watch* // @icon https://www.youtube.com/favicon.ico // @grant none // ==/UserScript== (function () { 'use strict'; // ============================================================ // ★ 設定エリア ここだけ編集すればOK // ============================================================ const TARGET_VIDEO_ID = '_SCd8LV3Eww'; // スキップ設定 // { from: 'H:MM:SS', to: 'H:MM:SS' } の形式で何個でも追加・削除できます const SKIPS = [ { from: '1:55:19', to: '2:22:17' }, { from: '4:20:26', to: '4:47:24' }, // { from: '6:00:00', to: '6:30:00' }, // ← こんな感じで追加 ]; // ============================================================ // 以下は編集不要 // ============================================================ function toSec(str) { const parts = str.split(':').map(Number); if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2]; if (parts.length === 2) return parts[0] * 60 + parts[1]; return parts[0]; } const SKIP_LIST = SKIPS.map(sk => ({ from: toSec(sk.from), to: toSec(sk.to), label: sk.from + ' → ' + sk.to, })); const TOLERANCE = 2; let video = null; let skipped = SKIP_LIST.map(() => false); let indicator = null; function getVideoId() { return new URLSearchParams(window.location.search).get('v'); } function createIndicator() { if (indicator) return; indicator = document.createElement('div'); Object.assign(indicator.style, { position: 'fixed', bottom: '80px', right: '20px', background: 'rgba(0,0,0,0.78)', color: '#fff', padding: '8px 14px', borderRadius: '8px', fontSize: '13px', zIndex: '9999', pointerEvents: 'none', opacity: '0', transition: 'opacity 0.3s', maxWidth: '280px', lineHeight: '1.6', whiteSpace: 'pre-line', }); document.body.appendChild(indicator); } function showIndicator(msg, duration) { if (!indicator) createIndicator(); indicator.textContent = msg; indicator.style.opacity = '1'; clearTimeout(indicator._t); indicator._t = setTimeout(() => { indicator.style.opacity = '0'; }, duration || 3500); } function startWatching() { video = document.querySelector('video'); if (!video) { setTimeout(startWatching, 1000); return; } createIndicator(); showIndicator('⏱ 休憩スキップ: 有効\n設定数: ' + SKIP_LIST.length + ' 個', 4000); video.addEventListener('timeupdate', () => { if (getVideoId() !== TARGET_VIDEO_ID) return; const cur = video.currentTime; SKIP_LIST.forEach((sk, i) => { if (!skipped[i] && cur >= sk.from && cur < sk.from + TOLERANCE) { skipped[i] = true; video.currentTime = sk.to; showIndicator('⏭ スキップ ' + (i + 1) + '\n' + sk.label, 4000); } if (cur < sk.from - 10) skipped[i] = false; }); }); } function reset() { video = null; skipped = SKIP_LIST.map(() => false); indicator = null; } function init() { if (getVideoId() !== TARGET_VIDEO_ID) return; startWatching(); } let lastUrl = location.href; new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; reset(); setTimeout(init, 1500); } }).observe(document.body, { childList: true, subtree: true }); init(); })();