Automatically skips to the next unlocked essential video and auto-plays videos
// ==UserScript==
// @name N予備校動画自動切り替え
// @namespace http://tampermonkey.net/
// @version 2.0
// @description Automatically skips to the next unlocked essential video and auto-plays videos
// @match https://www.nnn.ed.nico/*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Auto Skip Variables
let lastUrl = location.href;
let lastClickTime = 0;
const CLICK_COOLDOWN = 2000; // 2 seconds cooldown between clicks
// Auto Play Variables
const STOP_BEFORE_END = 10; // Seconds before the end to stop auto-play
let isAutoPlayEnabled = true;
// Auto Skip Functions
function isEssentialVideo(element) {
const textbookRef = element.querySelector('.sc-rux73v-0');
return textbookRef && textbookRef.textContent.includes('教科書');
}
function getVideoNumber(element) {
const numberSpan = element.querySelector('span[font-size="1.5rem"]');
if (numberSpan) {
const match = numberSpan.textContent.match(/(\d+)\./);
return match ? parseInt(match[1]) : 0;
}
return 0;
}
function isVideoUnlocked(element) {
const container = element.closest('.sc-1otp79h-0');
return container && !container.classList.contains('hoWVG');
}
function findNextEssentialVideo() {
const videoElements = Array.from(document.querySelectorAll('.sc-lcfvsp-10'));
const currentVideo = videoElements.find(el => !el.classList.contains('cqkVcF'));
if (currentVideo) {
const currentIndex = videoElements.indexOf(currentVideo);
const currentNumber = getVideoNumber(currentVideo);
for (let i = currentIndex + 1; i < videoElements.length; i++) {
const video = videoElements[i];
if (isEssentialVideo(video) && isVideoUnlocked(video)) {
const videoNumber = getVideoNumber(video);
if (videoNumber > currentNumber) {
return video;
}
}
}
}
return null;
}
function clickNextEssentialVideo() {
console.log('次に飛ばす動画を検索中');
const nextVideo = findNextEssentialVideo();
if (nextVideo) {
console.log('Found next essential video:', nextVideo);
const currentTime = Date.now();
if (currentTime - lastClickTime >= CLICK_COOLDOWN) {
console.log('次の動画をクリック中');
nextVideo.click();
lastClickTime = currentTime;
setTimeout(checkUrlAndContinue, 1000);
} else {
console.log('コールダウン実施中クリックまでに待ってください');
setTimeout(clickNextEssentialVideo, CLICK_COOLDOWN - (currentTime - lastClickTime));
}
} else {
console.log('必修動画が見れないもしくは全て解除されていない状態です');
setTimeout(clickNextEssentialVideo, 5000); // Retry after 5 seconds
}
}
function checkUrlAndContinue() {
const currentUrl = location.href;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
setTimeout(clickNextEssentialVideo, 2000);
} else {
setTimeout(checkUrlAndContinue, 1000);
}
}
// Auto Play Functions
function playVideo(event) {
if (isAutoPlayEnabled) {
event.target.play();
console.log('自動的に動画を再開');
}
}
function checkTimeToDisable(video) {
const timeRemaining = video.duration - video.currentTime;
if (timeRemaining <= STOP_BEFORE_END) {
isAutoPlayEnabled = false;
console.log('終わりの10前につき自動再開を停止');
} else {
isAutoPlayEnabled = true;
}
}
function addPlayListener() {
const videoElements = document.querySelectorAll('video');
videoElements.forEach(video => {
video.removeEventListener('pause', playVideo); // Remove existing listener to avoid duplication
video.addEventListener('pause', playVideo); // Add pause event listener to play video
video.addEventListener('timeupdate', () => checkTimeToDisable(video)); // Check time remaining on each time update
});
}
// Set up observers
const skipObserver = new MutationObserver((mutations) => {
if (location.href !== lastUrl) {
lastUrl = location.href;
console.log('URL changed. Attempting to click next essential video element...');
setTimeout(clickNextEssentialVideo, 2000);
}
});
const playObserver = new MutationObserver(() => {
addPlayListener();
});
// Start observing
skipObserver.observe(document, { subtree: true, childList: true });
playObserver.observe(document, { childList: true, subtree: true });
console.log('N予備校動画自動切り替えコードロード完了');
// Initial runs
setTimeout(clickNextEssentialVideo, 2000);
addPlayListener();
})();