Automatically clicks the next chapter button after customizable time with audio controls and navigation features
目前為
// ==UserScript==
// @name 5-Minute Auto-Next Chapter with Audio Controls
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Automatically clicks the next chapter button after customizable time with audio controls and navigation features
// @author You
// @match https://inovel12.com/*
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
// Configuration
const DEFAULT_COUNTDOWN_MINUTES = 5;
const DEFAULT_MAX_CHAPTERS = 4; // Default maximum number of chapters
// Load user preferences from localStorage or use defaults
let userSettings = JSON.parse(localStorage.getItem('autoNextSettings')) || {};
let COUNTDOWN_MINUTES = userSettings.timerMinutes || DEFAULT_COUNTDOWN_MINUTES;
let MAX_CHAPTERS = userSettings.maxChapters || DEFAULT_MAX_CHAPTERS;
let isMinimized = userSettings.isMinimized || false;
let audioPlaybackRate = userSettings.audioPlaybackRate || 0.7; // Default to 0.7x
// Selector for the next chapter button
const NEXT_BUTTON_SELECTOR = 'a.nextchap[rel="next"]';
// Selector for the previous chapter button
const PREV_BUTTON_SELECTOR = 'a.prevchap[rel="prev"]';
// Selector for the audio element
const AUDIO_SELECTOR = 'audio[controls]';
// Convert minutes to milliseconds
let countdownMs = COUNTDOWN_MINUTES * 60 * 1000;
// Chapter counter - initialize or retrieve from session storage
let chaptersNavigated = parseInt(sessionStorage.getItem('auto_next_chapters_count') || '0');
// Check if this is a "next chapter" page by checking session storage
const isFirstPage = !sessionStorage.getItem('auto_next_started');
// Timer states
let isRunning = false;
let isPaused = false;
let startTime = 0;
let endTime = 0;
let remainingTime = countdownMs;
let countdownInterval;
let settingsPanelOpen = false;
// Audio states
let audioElement = null;
let isAudioPlaying = false;
// Create main container
const mainContainer = document.createElement('div');
mainContainer.style.cssText = `
position: fixed;
bottom: 80px;
left: 20px;
z-index: 9999;
`;
document.body.appendChild(mainContainer);
// Create the expanded view container
const expandedView = document.createElement('div');
expandedView.className = 'auto-next-expanded';
expandedView.style.cssText = `
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 15px;
border-radius: 8px;
font-size: 16px;
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
min-width: 180px;
max-width: 250px;
touch-action: manipulation;
`;
// Create minimized bubble view with timer
const bubbleView = document.createElement('div');
bubbleView.className = 'auto-next-bubble';
bubbleView.style.cssText = `
width: 70px;
height: 70px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
padding: 5px;
`;
// Create timer icon
const bubbleIcon = document.createElement('div');
bubbleIcon.style.cssText = `
font-size: 20px;
margin-bottom: 2px;
`;
bubbleIcon.innerHTML = '⏱️';
// Create timer text for bubble
const bubbleTimer = document.createElement('div');
bubbleTimer.style.cssText = `
font-size: 12px;
color: white;
font-weight: bold;
`;
bubbleTimer.textContent = COUNTDOWN_MINUTES + ':00';
bubbleView.appendChild(bubbleIcon);
bubbleView.appendChild(bubbleTimer);
// Function to toggle between views
function toggleView() {
isMinimized = !isMinimized;
updateViewState();
// Save state
userSettings.isMinimized = isMinimized;
localStorage.setItem('autoNextSettings', JSON.stringify(userSettings));
}
// Function to update the view based on minimized state
function updateViewState() {
if (isMinimized) {
// Show bubble view, hide expanded view
if (mainContainer.contains(expandedView)) {
mainContainer.removeChild(expandedView);
}
if (!mainContainer.contains(bubbleView)) {
mainContainer.appendChild(bubbleView);
}
} else {
// Show expanded view, hide bubble view
if (mainContainer.contains(bubbleView)) {
mainContainer.removeChild(bubbleView);
}
if (!mainContainer.contains(expandedView)) {
mainContainer.appendChild(expandedView);
}
}
}
// Add click handler to bubble
bubbleView.addEventListener('click', toggleView);
// Create timer text element
const timerText = document.createElement('div');
timerText.className = 'timer-text';
timerText.style.cssText = `
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
text-align: center;
width: 100%;
`;
timerText.textContent = `Next chapter in: ${COUNTDOWN_MINUTES}:00`;
expandedView.appendChild(timerText);
// Create chapter counter text element
const chapterCounterText = document.createElement('div');
chapterCounterText.className = 'chapter-counter-text';
chapterCounterText.style.cssText = `
font-size: 14px;
color: #ffcc00;
margin-bottom: 5px;
text-align: center;
width: 100%;
`;
chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS}`;
expandedView.appendChild(chapterCounterText);
// Create settings panel (initially hidden)
const settingsPanel = document.createElement('div');
settingsPanel.style.cssText = `
background-color: rgba(40, 40, 40, 0.95);
padding: 12px;
border-radius: 5px;
margin-top: 8px;
display: none;
width: 100%;
`;
// Create minutes input with label
const timerSettingContainer = document.createElement('div');
timerSettingContainer.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
`;
const timerLabel = document.createElement('label');
timerLabel.textContent = 'Timer (minutes):';
timerLabel.style.marginRight = '10px';
const timerInput = document.createElement('input');
timerInput.type = 'number';
timerInput.min = '0.5';
timerInput.max = '60';
timerInput.step = '0.5';
timerInput.value = COUNTDOWN_MINUTES;
timerInput.style.cssText = `
width: 60px;
background-color: #333;
color: white;
border: 1px solid #555;
border-radius: 3px;
padding: 4px;
`;
timerSettingContainer.appendChild(timerLabel);
timerSettingContainer.appendChild(timerInput);
// Create max chapters input with label
const maxChaptersContainer = document.createElement('div');
maxChaptersContainer.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
`;
const maxChaptersLabel = document.createElement('label');
maxChaptersLabel.textContent = 'Max Chapters:';
maxChaptersLabel.style.marginRight = '10px';
const maxChaptersInput = document.createElement('input');
maxChaptersInput.type = 'number';
maxChaptersInput.min = '1';
maxChaptersInput.max = '20';
maxChaptersInput.step = '1';
maxChaptersInput.value = MAX_CHAPTERS;
maxChaptersInput.style.cssText = `
width: 60px;
background-color: #333;
color: white;
border: 1px solid #555;
border-radius: 3px;
padding: 4px;
`;
maxChaptersContainer.appendChild(maxChaptersLabel);
maxChaptersContainer.appendChild(maxChaptersInput);
// Create audio playback rate slider with label
const audioRateContainer = document.createElement('div');
audioRateContainer.style.cssText = `
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 10px;
width: 100%;
`;
const audioRateLabel = document.createElement('label');
audioRateLabel.textContent = 'Audio Speed:';
audioRateLabel.style.marginBottom = '5px';
const audioRateSliderContainer = document.createElement('div');
audioRateSliderContainer.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
`;
const audioRateSlider = document.createElement('input');
audioRateSlider.type = 'range';
audioRateSlider.min = '0.5';
audioRateSlider.max = '2.5';
audioRateSlider.step = '0.1';
audioRateSlider.value = audioPlaybackRate;
audioRateSlider.style.cssText = `
flex-grow: 1;
margin-right: 10px;
`;
const audioRateValue = document.createElement('span');
audioRateValue.textContent = audioPlaybackRate + 'x';
audioRateValue.style.width = '30px';
audioRateSliderContainer.appendChild(audioRateSlider);
audioRateSliderContainer.appendChild(audioRateValue);
audioRateContainer.appendChild(audioRateLabel);
audioRateContainer.appendChild(audioRateSliderContainer);
// Add event listener for audio rate slider
audioRateSlider.addEventListener('input', function() {
const newRate = parseFloat(this.value);
audioRateValue.textContent = newRate.toFixed(1) + 'x';
setAudioPlaybackRate(newRate);
});
// Create save and cancel buttons
const settingsButtonContainer = document.createElement('div');
settingsButtonContainer.style.cssText = `
display: flex;
justify-content: space-between;
margin-top: 10px;
`;
const saveButton = document.createElement('button');
saveButton.textContent = 'Save';
saveButton.style.cssText = `
background-color: #4CAF50;
border: none;
color: white;
padding: 5px 10px;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
`;
const cancelButton = document.createElement('button');
cancelButton.textContent = 'Cancel';
cancelButton.style.cssText = `
background-color: #f44336;
border: none;
color: white;
padding: 5px 10px;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
`;
settingsButtonContainer.appendChild(saveButton);
settingsButtonContainer.appendChild(cancelButton);
// Add components to settings panel
settingsPanel.appendChild(timerSettingContainer);
settingsPanel.appendChild(maxChaptersContainer);
settingsPanel.appendChild(audioRateContainer);
settingsPanel.appendChild(settingsButtonContainer);
// Add settings panel to expanded view
expandedView.appendChild(settingsPanel);
// Create a button container for all controls
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 5px;
width: 100%;
`;
expandedView.appendChild(buttonContainer);
// Create the main multi-function button (Row 1)
const actionButton = document.createElement('button');
actionButton.textContent = isFirstPage ? 'Start' : 'Pause';
actionButton.style.cssText = `
background-color: ${isFirstPage ? '#2196F3' : '#4CAF50'};
border: none;
color: white;
padding: 10px 15px;
text-align: center;
text-decoration: none;
font-size: 17px;
font-weight: bold;
cursor: pointer;
border-radius: 6px;
width: 100%;
`;
buttonContainer.appendChild(actionButton);
// Create container for time adjustment buttons (Row 2)
const adjustButtonsContainer = document.createElement('div');
adjustButtonsContainer.style.cssText = `
display: flex;
justify-content: space-between;
width: 100%;
gap: 10px;
`;
// Create -30s button
const minusButton = document.createElement('button');
minusButton.textContent = '-30s';
minusButton.style.cssText = `
background-color: #FF9800;
border: none;
color: white;
padding: 8px 0;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
flex: 1;
`;
// Create +30s button
const plusButton = document.createElement('button');
plusButton.textContent = '+30s';
plusButton.style.cssText = `
background-color: #9C27B0;
border: none;
color: white;
padding: 8px 0;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
flex: 1;
`;
// Add time adjustment buttons to their container
adjustButtonsContainer.appendChild(minusButton);
adjustButtonsContainer.appendChild(plusButton);
buttonContainer.appendChild(adjustButtonsContainer);
// Create container for audio control buttons (New Row)
const audioControlContainer = document.createElement('div');
audioControlContainer.style.cssText = `
display: flex;
justify-content: space-between;
width: 100%;
gap: 10px;
`;
// Create audio play/pause button
const audioToggleButton = document.createElement('button');
audioToggleButton.innerHTML = '▶️ Play Audio';
audioToggleButton.style.cssText = `
background-color: #03A9F4;
border: none;
color: white;
padding: 8px 0;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
flex: 2;
`;
// Create speed down button
const speedDownButton = document.createElement('button');
speedDownButton.innerHTML = '🐢';
speedDownButton.style.cssText = `
background-color: #795548;
border: none;
color: white;
padding: 8px 0;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
flex: 1;
`;
// Create speed up button
const speedUpButton = document.createElement('button');
speedUpButton.innerHTML = '🐇';
speedUpButton.style.cssText = `
background-color: #009688;
border: none;
color: white;
padding: 8px 0;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
flex: 1;
`;
// Add audio control buttons to their container
audioControlContainer.appendChild(audioToggleButton);
audioControlContainer.appendChild(speedDownButton);
audioControlContainer.appendChild(speedUpButton);
buttonContainer.appendChild(audioControlContainer);
// Create container for minimize and settings buttons
const controlButtonsContainer = document.createElement('div');
controlButtonsContainer.style.cssText = `
display: flex;
justify-content: space-between;
width: 100%;
gap: 10px;
`;
// Create minimize button with text and icon
const minimizeButton = document.createElement('button');
minimizeButton.innerHTML = '− Minimize';
minimizeButton.style.cssText = `
background-color: #607D8B;
border: none;
color: white;
padding: 8px 0;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
flex: 1;
`;
minimizeButton.addEventListener('click', toggleView);
// Create settings button with text and icon
const settingsButton = document.createElement('button');
settingsButton.innerHTML = '⚙️ Settings';
settingsButton.style.cssText = `
background-color: #2196F3;
border: none;
color: white;
padding: 8px 0;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
flex: 1;
`;
// Add minimize and settings buttons to their container
controlButtonsContainer.appendChild(minimizeButton);
controlButtonsContainer.appendChild(settingsButton);
buttonContainer.appendChild(controlButtonsContainer);
// Create Stop button
const stopButton = document.createElement('button');
stopButton.textContent = '⛔ Stop Permanently';
stopButton.style.cssText = `
background-color: #f44336;
border: none;
color: white;
padding: 8px 0;
text-align: center;
text-decoration: none;
font-size: 14px;
font-weight: bold;
cursor: pointer;
border-radius: 4px;
width: 100%;
margin-top: 5px;
`;
buttonContainer.appendChild(stopButton);
// Create container for navigation buttons
const navButtonsContainer = document.createElement('div');
navButtonsContainer.style.cssText = `
display: flex;
justify-content: space-between;
width: 100%;
gap: 10px;
margin-top: 10px;
`;
// Create Previous Chapter button
const prevChapterButton = document.createElement('button');
prevChapterButton.innerHTML = '⬅️ Previous';
prevChapterButton.style.cssText = `
background-color: #FF9800;
border: none;
color: white;
padding: 8px 0;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
flex: 1;
`;
// Create Next Chapter button
const nextChapterButton = document.createElement('button');
nextChapterButton.innerHTML = 'Next ➡️';
nextChapterButton.style.cssText = `
background-color: #4CAF50;
border: none;
color: white;
padding: 8px 0;
text-align: center;
text-decoration: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
flex: 1;
`;
// Add navigation buttons to their container
navButtonsContainer.appendChild(prevChapterButton);
navButtonsContainer.appendChild(nextChapterButton);
buttonContainer.appendChild(navButtonsContainer);
// Function to get the audio element
function getAudioElement() {
if (!audioElement) {
audioElement = document.querySelector(AUDIO_SELECTOR);
}
return audioElement;
}
// Function to toggle audio play/pause
function toggleAudio() {
const audio = getAudioElement();
if (audio) {
if (audio.paused) {
audio.play();
isAudioPlaying = true;
audioToggleButton.innerHTML = '⏸️ Pause Audio';
} else {
audio.pause();
isAudioPlaying = false;
audioToggleButton.innerHTML = '▶️ Play Audio';
}
} else {
audioToggleButton.innerHTML = '❌ No Audio Found';
setTimeout(() => {
audioToggleButton.innerHTML = isAudioPlaying ? '⏸️ Pause Audio' : '▶️ Play Audio';
}, 2000);
}
}
// Function to set audio playback rate
function setAudioPlaybackRate(rate) {
const audio = getAudioElement();
if (audio) {
audio.playbackRate = rate;
audioPlaybackRate = rate;
// Save to user settings
userSettings.audioPlaybackRate = rate;
localStorage.setItem('autoNextSettings', JSON.stringify(userSettings));
}
}
// Function to decrease audio speed
function decreaseAudioSpeed() {
const newRate = Math.max(0.5, audioPlaybackRate - 0.1);
setAudioPlaybackRate(newRate);
// Update slider and display
if (audioRateSlider) {
audioRateSlider.value = newRate;
audioRateValue.textContent = newRate.toFixed(1) + 'x';
}
// Show temporary feedback
const originalText = speedDownButton.innerHTML;
speedDownButton.innerHTML = newRate.toFixed(1) + 'x';
setTimeout(() => {
speedDownButton.innerHTML = originalText;
}, 1000);
}
// Function to increase audio speed
function increaseAudioSpeed() {
const newRate = Math.min(2.5, audioPlaybackRate + 0.1);
setAudioPlaybackRate(newRate);
// Update slider and display
if (audioRateSlider) {
audioRateSlider.value = newRate;
audioRateValue.textContent = newRate.toFixed(1) + 'x';
}
// Show temporary feedback
const originalText = speedUpButton.innerHTML;
speedUpButton.innerHTML = newRate.toFixed(1) + 'x';
setTimeout(() => {
speedUpButton.innerHTML = originalText;
}, 1000);
}
// Function to toggle settings panel
function toggleSettingsPanel() {
settingsPanelOpen = !settingsPanelOpen;
settingsPanel.style.display = settingsPanelOpen ? 'block' : 'none';
// Reset input values to current settings
timerInput.value = COUNTDOWN_MINUTES;
maxChaptersInput.value = MAX_CHAPTERS;
audioRateSlider.value = audioPlaybackRate;
audioRateValue.textContent = audioPlaybackRate.toFixed(1) + 'x';
}
// Function to save settings
function saveSettings() {
// Get and validate timer minutes
const newTimerMinutes = parseFloat(timerInput.value);
if (isNaN(newTimerMinutes) || newTimerMinutes < 0.5 || newTimerMinutes > 60) {
alert('Please enter a valid time between 0.5 and 60 minutes.');
return;
}
// Get and validate max chapters
const newMaxChapters = parseInt(maxChaptersInput.value);
if (isNaN(newMaxChapters) || newMaxChapters < 1 || newMaxChapters > 20) {
alert('Please enter a valid number of chapters between 1 and 20.');
return;
}
// Update settings
COUNTDOWN_MINUTES = newTimerMinutes;
countdownMs = COUNTDOWN_MINUTES * 60 * 1000;
// Update max chapters
MAX_CHAPTERS = newMaxChapters;
chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS}`;
// Get and save audio rate
const newAudioRate = parseFloat(audioRateSlider.value);
setAudioPlaybackRate(newAudioRate);
// If timer is not running, update the remaining time
if (!isRunning) {
remainingTime = countdownMs;
updateCountdown();
}
// Save to localStorage
userSettings.timerMinutes = COUNTDOWN_MINUTES;
userSettings.maxChapters = MAX_CHAPTERS;
userSettings.audioPlaybackRate = audioPlaybackRate;
localStorage.setItem('autoNextSettings', JSON.stringify(userSettings));
// Close settings panel
toggleSettingsPanel();
}
// Settings button click handler
settingsButton.addEventListener('click', toggleSettingsPanel);
// Save button click handler
saveButton.addEventListener('click', saveSettings);
// Cancel button click handler
cancelButton.addEventListener('click', toggleSettingsPanel);
// Function to start the timer
function startTimer() {
isRunning = true;
isPaused = false;
startTime = Date.now();
endTime = startTime + remainingTime;
// Store in session storage that we've started
sessionStorage.setItem('auto_next_started', 'true');
// Update button
actionButton.textContent = 'Pause';
actionButton.style.backgroundColor = '#4CAF50';
// Start the countdown interval
if (!countdownInterval) {
countdownInterval = setInterval(updateCountdown, 1000);
}
}
// Function to pause the timer
function pauseTimer() {
isPaused = true;
isRunning = false;
// Store the remaining time when paused
remainingTime = Math.max(0, endTime - Date.now());
// Update button
actionButton.textContent = 'Resume';
actionButton.style.backgroundColor = '#f44336';
}
// Function to resume the timer
function resumeTimer() {
isPaused = false;
isRunning = true;
// Recalculate the end time based on the remaining time
endTime = Date.now() + remainingTime;
// Update button
actionButton.textContent = 'Pause';
actionButton.style.backgroundColor = '#4CAF50';
}
// Update the countdown display
function updateCountdown() {
if (isRunning && !isPaused) {
remainingTime = Math.max(0, endTime - Date.now());
}
const minutesLeft = Math.floor(remainingTime / 60000);
const secondsLeft = Math.floor((remainingTime % 60000) / 1000);
const formattedTime = `${minutesLeft}:${secondsLeft.toString().padStart(2, '0')}`;
// Update the timer text in expanded view
timerText.textContent = `Next chapter in: ${formattedTime}`;
// Update the timer text in bubble view
if (bubbleTimer) {
bubbleTimer.textContent = formattedTime;
}
if (remainingTime <= 0 && isRunning && !isPaused) {
clearInterval(countdownInterval);
countdownInterval = null;
clickNextChapter();
}
}
// Button click handler - cycles through Start, Pause, Resume
actionButton.addEventListener('click', function() {
if (!isRunning && !isPaused) {
// Start the timer
startTimer();
} else if (isRunning && !isPaused) {
// Pause the timer
pauseTimer();
} else if (!isRunning && isPaused) {
// Resume the timer
resumeTimer();
}
});
// Add 30 seconds to the timer
plusButton.addEventListener('click', function() {
// Only allow adjustment if timer is running or paused
if (isRunning || isPaused) {
// If paused, just adjust the remaining time
if (isPaused) {
remainingTime += 30000; // 30 seconds in milliseconds
} else {
// If running, adjust the end time
endTime += 30000;
}
// Update the display immediately
updateCountdown();
}
});
// Subtract 30 seconds from the timer
minusButton.addEventListener('click', function() {
// Only allow adjustment if timer is running or paused
if (isRunning || isPaused) {
if (isPaused) {
// Don't let it go below zero
remainingTime = Math.max(0, remainingTime - 30000);
} else {
// Adjust end time but don't let it go below current time
endTime = Math.max(Date.now(), endTime - 30000);
// Recalculate remaining time
remainingTime = Math.max(0, endTime - Date.now());
}
// Update the display immediately
updateCountdown();
// If we reduced to zero, trigger next chapter
if (remainingTime <= 0 && isRunning) {
clearInterval(countdownInterval);
countdownInterval = null;
clickNextChapter();
}
}
});
// Audio control button click handlers
audioToggleButton.addEventListener('click', toggleAudio);
speedDownButton.addEventListener('click', decreaseAudioSpeed);
speedUpButton.addEventListener('click', increaseAudioSpeed);
// Function to check if we should stop due to chapter limit
function checkChapterLimit() {
if (chaptersNavigated >= MAX_CHAPTERS) {
// Update the UI to show we've reached the limit
timerText.textContent = `Reached limit of ${MAX_CHAPTERS} chapters`;
chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS} - Limit reached!`;
chapterCounterText.style.color = '#ff6666';
// Reset the chapter counter after reaching the limit
chaptersNavigated = 0;
sessionStorage.setItem('auto_next_chapters_count', '0');
// Stop the timer
stopPermanently();
return true;
}
return false;
}
// Function to click the next chapter button
function clickNextChapter() {
// Increment chapter counter before checking
chaptersNavigated++;
sessionStorage.setItem('auto_next_chapters_count', chaptersNavigated.toString());
// Update chapter counter display
chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS}`;
// Check if we've reached the limit
if (checkChapterLimit()) {
// We've reached the limit, don't proceed
return;
}
// Update the timer text
timerText.textContent = 'Moving to next chapter...';
// Try to find the next chapter button
const nextButton = document.querySelector(NEXT_BUTTON_SELECTOR);
if (nextButton) {
// Highlight the button being clicked
const originalBackground = nextButton.style.backgroundColor;
const originalTransition = nextButton.style.transition;
nextButton.style.transition = 'background-color 0.3s ease';
nextButton.style.backgroundColor = 'yellow';
// Store flag to auto-play audio after navigation
sessionStorage.setItem('auto_play_audio', 'true');
// Click after a short delay to show the highlight
setTimeout(() => {
nextButton.click();
// If for some reason we're still on the page after clicking
setTimeout(() => {
nextButton.style.backgroundColor = originalBackground;
nextButton.style.transition = originalTransition;
timerText.textContent = 'Click failed or redirecting...';
}, 1000);
}, 500);
} else {
timerText.textContent = 'Next button not found! Adjust the selector in the script.';
// Error message will remain visible
}
}
// Function to permanently stop the script
function stopPermanently() {
// Clear any running intervals
if (countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
// Reset states
isRunning = false;
isPaused = false;
// Reset chapter counter
chaptersNavigated = 0;
sessionStorage.setItem('auto_next_chapters_count', '0');
// Update UI
timerText.textContent = 'Timer stopped permanently';
chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS}`;
chapterCounterText.style.color = '#ffcc00'; // Reset color
if (bubbleTimer) {
bubbleTimer.textContent = 'Stopped';
}
// Disable all buttons except settings and audio controls
actionButton.disabled = true;
minusButton.disabled = true;
plusButton.disabled = true;
stopButton.disabled = true;
prevChapterButton.disabled = true;
nextChapterButton.disabled = true;
// Change button appearances
actionButton.style.backgroundColor = '#999';
minusButton.style.backgroundColor = '#999';
plusButton.style.backgroundColor = '#999';
stopButton.style.backgroundColor = '#999';
prevChapterButton.style.backgroundColor = '#999';
nextChapterButton.style.backgroundColor = '#999';
stopButton.textContent = 'Stopped';
}
// Function to navigate to previous chapter
function goToPrevChapter() {
const prevButton = document.querySelector(PREV_BUTTON_SELECTOR);
if (prevButton) {
// Highlight the button being clicked
const originalBackground = prevButton.style.backgroundColor;
const originalTransition = prevButton.style.transition;
prevButton.style.transition = 'background-color 0.3s ease';
prevButton.style.backgroundColor = 'yellow';
// Click after a short delay to show the highlight
setTimeout(() => {
prevButton.click();
}, 300);
} else {
timerText.textContent = 'Previous chapter button not found!';
}
}
// Function to navigate to next chapter immediately
function goToNextChapter() {
const nextButton = document.querySelector(NEXT_BUTTON_SELECTOR);
if (nextButton) {
// Increment chapter counter (just like the auto-next function)
chaptersNavigated++;
sessionStorage.setItem('auto_next_chapters_count', chaptersNavigated.toString());
// Update chapter counter display
chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS}`;
// Check if we've reached the limit before navigating
if (checkChapterLimit()) {
// We've reached the limit, don't proceed
return;
}
// Highlight the button being clicked
const originalBackground = nextButton.style.backgroundColor;
const originalTransition = nextButton.style.transition;
nextButton.style.transition = 'background-color 0.3s ease';
nextButton.style.backgroundColor = 'yellow';
// Store flag to auto-play audio after navigation
sessionStorage.setItem('auto_play_audio', 'true');
// Click after a short delay to show the highlight
setTimeout(() => {
nextButton.click();
}, 300);
} else {
timerText.textContent = 'Next chapter button not found!';
}
}
// Add stop button click handler
stopButton.addEventListener('click', stopPermanently);
// Add click handlers for navigation buttons
prevChapterButton.addEventListener('click', goToPrevChapter);
nextChapterButton.addEventListener('click', goToNextChapter);
// Initialize audio controls
function initializeAudio() {
// Try to get the audio element
const audio = getAudioElement();
if (audio) {
// Set the initial playback rate
audio.playbackRate = audioPlaybackRate;
// Check if audio is playing and update UI accordingly
isAudioPlaying = !audio.paused;
audioToggleButton.innerHTML = isAudioPlaying ? '⏸️ Pause Audio' : '▶️ Play Audio';
// Add an event listener to update UI when audio state changes
audio.addEventListener('play', function() {
isAudioPlaying = true;
audioToggleButton.innerHTML = '⏸️ Pause Audio';
});
audio.addEventListener('pause', function() {
isAudioPlaying = false;
audioToggleButton.innerHTML = '▶️ Play Audio';
});
// Add an event listener to handle audio ending
audio.addEventListener('ended', function() {
isAudioPlaying = false;
audioToggleButton.innerHTML = '▶️ Play Audio';
});
// Check if we should auto-play audio after navigation
if (sessionStorage.getItem('auto_play_audio') === 'true') {
// Clear the flag
sessionStorage.removeItem('auto_play_audio');
// Add notification
timerText.textContent = 'Auto-playing audio in 3 seconds...';
// Auto-play after 3 seconds
setTimeout(() => {
audio.play();
timerText.textContent = 'Audio playing automatically';
// Restore normal timer display after notification
setTimeout(() => {
updateCountdown();
}, 2000);
}, 3000);
}
}
}
// Function to handle page visibility changes
function handleVisibilityChange() {
if (document.hidden) {
// Page is hidden (user switched tabs, minimized window, etc.)
// No need to pause audio automatically
} else {
// Page is visible again
// Reinitialize audio controls to sync with actual state
initializeAudio();
}
}
// Add visibility change listener
document.addEventListener('visibilitychange', handleVisibilityChange);
// Check if we've already reached the chapter limit
if (chaptersNavigated >= MAX_CHAPTERS) {
checkChapterLimit();
} else {
// Set initial view state based on preference
updateViewState();
// Initialize audio controls
initializeAudio();
// Automatically start timer if it's not the first page
if (!isFirstPage) {
startTimer();
}
// Update countdown initially
updateCountdown();
}
// Check for new audio elements if they get added dynamically
const audioObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
// Check if any of the added nodes are audio elements or contain them
mutation.addedNodes.forEach(function(node) {
if (node.nodeName === 'AUDIO' ||
(node.nodeType === 1 && node.querySelector(AUDIO_SELECTOR))) {
// Reset audio element cache and reinitialize
audioElement = null;
initializeAudio();
}
});
}
});
});
// Start observing the document with the configured parameters
audioObserver.observe(document.body, { childList: true, subtree: true });
})();