// ==UserScript==
// @name Redirect YouTube to WaniKani 30
// @namespace http://tampermonkey.net/
// @version 3.1
// @description Lockout window for WaniKani reviews, prompt for API token if not set, check hourly, and optionally check for lessons. Active only between 5 AM and 11 PM. Opens WaniKani in a new tab and pauses YouTube video. Supports snooze functionality.
// @author Your Name
// @match https://www.youtube.com/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let lockoutDiv, lockoutMessage, reviewCounter, triggeredByButton = false;
// Function to create and show the lockout window
function showLockoutWindow(reviews) {
if (!document.body) {
console.error('Document body not available.');
return;
}
if (!lockoutDiv) {
lockoutDiv = document.createElement('div');
lockoutDiv.style.position = 'fixed';
lockoutDiv.style.top = '0';
lockoutDiv.style.left = '0';
lockoutDiv.style.width = '100%';
lockoutDiv.style.height = '100%';
lockoutDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.9)';
lockoutDiv.style.color = 'white';
lockoutDiv.style.zIndex = '10000';
lockoutDiv.style.display = 'flex';
lockoutDiv.style.flexDirection = 'column';
lockoutDiv.style.alignItems = 'center';
lockoutDiv.style.justifyContent = 'center';
lockoutDiv.style.fontFamily = 'Arial, sans-serif';
lockoutDiv.style.padding = '20px';
lockoutDiv.style.boxSizing = 'border-box';
lockoutMessage = document.createElement('div');
lockoutMessage.style.fontSize = '24px';
lockoutMessage.style.marginBottom = '20px';
lockoutMessage.setAttribute('role', 'alert');
lockoutMessage.setAttribute('aria-live', 'assertive');
lockoutDiv.appendChild(lockoutMessage);
reviewCounter = document.createElement('div');
reviewCounter.style.fontSize = '18px';
reviewCounter.style.marginBottom = '30px';
lockoutDiv.appendChild(reviewCounter);
let checkApiButton = document.createElement('button');
checkApiButton.innerText = 'Check API';
checkApiButton.style.padding = '10px 20px';
checkApiButton.style.fontSize = '16px';
checkApiButton.style.cursor = 'pointer';
checkApiButton.style.backgroundColor = '#2E8B57';
checkApiButton.style.color = 'white';
checkApiButton.style.border = 'none';
checkApiButton.style.borderRadius = '5px';
checkApiButton.onclick = function() {
triggeredByButton = true; // Set flag to indicate button click
checkWaniKani();
};
checkApiButton.setAttribute('aria-label', 'Check API for updates');
lockoutDiv.appendChild(checkApiButton);
let wanikaniButton = document.createElement('button');
wanikaniButton.innerText = 'Go to WaniKani';
wanikaniButton.style.padding = '10px 20px';
wanikaniButton.style.fontSize = '16px';
wanikaniButton.style.cursor = 'pointer';
wanikaniButton.style.backgroundColor = '#4682B4';
wanikaniButton.style.color = 'white';
wanikaniButton.style.border = 'none';
wanikaniButton.style.borderRadius = '5px';
wanikaniButton.style.marginTop = '10px';
wanikaniButton.onclick = () => {
window.open('https://www.wanikani.com', '_blank');
};
wanikaniButton.setAttribute('aria-label', 'Go to WaniKani in a new tab');
lockoutDiv.appendChild(wanikaniButton);
if (GM_getValue('enableSnooze', true)) {
let snoozeButton = document.createElement('button');
snoozeButton.innerText = 'Snooze for 1 hour';
snoozeButton.style.padding = '10px 20px';
snoozeButton.style.fontSize = '16px';
snoozeButton.style.cursor = 'pointer';
snoozeButton.style.backgroundColor = '#FFA500';
snoozeButton.style.color = 'white';
snoozeButton.style.border = 'none';
snoozeButton.style.borderRadius = '5px';
snoozeButton.style.marginTop = '10px';
snoozeButton.onclick = function() {
let lastSnooze = GM_getValue('lastSnooze', 0);
let now = Date.now();
if (now - lastSnooze >= 24 * 60 * 60 * 1000) { // 24 hours in milliseconds
GM_setValue('lastSnooze', now);
removeLockoutWindow();
setTimeout(checkWaniKani, 60 * 60 * 1000); // 1 hour in milliseconds
} else {
alert('Snooze can only be used once every 24 hours.');
}
};
snoozeButton.setAttribute('aria-label', 'Snooze for 1 hour');
lockoutDiv.appendChild(snoozeButton);
}
document.body.appendChild(lockoutDiv);
pauseYouTubeVideo(); // Pause the video when the lockout screen appears
}
lockoutMessage.innerText = 'Reviews are available! Complete your reviews on WaniKani.';
reviewCounter.innerText = `Remaining reviews: ${reviews}`;
}
// Function to pause the YouTube video
function pauseYouTubeVideo() {
const video = document.querySelector('video');
if (video) {
video.pause();
}
}
// Function to remove the lockout window
function removeLockoutWindow() {
if (lockoutDiv) {
lockoutDiv.remove();
lockoutDiv = null;
}
}
// Function to check WaniKani API
function checkWaniKani() {
let apiToken = GM_getValue('wanikaniApiToken');
let checkLessons = GM_getValue('checkLessons', null);
let enableSnooze = GM_getValue('enableSnooze', null);
let currentTime = Date.now();
let currentHour = new Date().getHours();
if (currentHour < 5 || currentHour >= 23) {
console.log('Outside of active hours (5 AM to 11 PM). No check performed.');
scheduleNextCheck(getNextAllowedTime());
return;
}
if (!apiToken) {
apiToken = prompt('Please enter your WaniKani API token:');
if (apiToken) {
GM_setValue('wanikaniApiToken', apiToken);
GM_setValue('checkLessons', null);
GM_setValue('enableSnooze', null);
alert('Your API token is stored. Note: Storing sensitive data in browser storage carries some risk.');
} else {
console.error('No API token provided.');
return;
}
}
if (checkLessons === null || enableSnooze === null) {
if (checkLessons === null) {
if (confirm('Would you like to activate lesson checking as well?')) {
GM_setValue('checkLessons', true);
} else {
GM_setValue('checkLessons', false);
}
}
if (enableSnooze === null) {
if (confirm('Would you like to enable the snooze functionality?')) {
GM_setValue('enableSnooze', true);
} else {
GM_setValue('enableSnooze', false);
}
}
scheduleNextCheck(getNextAllowedTime());
return;
}
let apiUrl = 'https://api.wanikani.com/v2/assignments?immediately_available_for_review=true';
if (checkLessons) {
apiUrl += '&immediately_available_for_lessons=true';
}
console.log('API URL:', apiUrl);
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
headers: {
'Authorization': `Bearer ${apiToken}`,
'Wanikani-Revision': '20170710'
},
onload: function(response) {
try {
if (response.status === 401) {
console.error('Invalid API token. Please update your token.');
GM_setValue('wanikaniApiToken', '');
alert('Invalid API token. Please enter a valid token.');
return;
}
const data = JSON.parse(response.responseText);
const totalCount = data.total_count;
console.log('Total Count:', totalCount);
if (totalCount > 0) {
showLockoutWindow(totalCount);
} else {
console.log('No reviews available. Lockout window can be closed.');
removeLockoutWindow();
if (triggeredByButton) {
triggerConfetti(); // Trigger confetti for celebration
}
}
GM_setValue('lastCheck', currentTime);
triggeredByButton = false; // Reset flag after API check
} catch (error) {
console.error('Error parsing API response:', error);
}
},
onerror: function(error) {
console.error('Error fetching data:', error);
}
});
scheduleNextCheck(getNextAllowedTime());
}
// Function to schedule the next check at the next allowed time
function scheduleNextCheck(nextCheckTime) {
let currentTime = Date.now();
let timeUntilNextCheck = nextCheckTime.getTime() - currentTime;
console.log(`Scheduling next check in ${timeUntilNextCheck / 1000} seconds.`);
setTimeout(checkWaniKani, timeUntilNextCheck);
}
// Function to get the next allowed time for checking (5 AM to 11 PM)
function getNextAllowedTime() {
let now = new Date();
let hour = now.getHours();
now.setMinutes(2, 0, 0); // Ensure the next check is always 2 minutes past the hour
if (hour < 5) {
now.setHours(5); // Set to 5:02 AM
} else if (hour >= 23) {
now.setHours(5); // Set to 5:02 AM next day
now.setDate(now.getDate() + 1);
} else {
now.setHours(hour + 1); // Next hour at 2 minutes past the hour
}
return now;
}
// Function to trigger confetti celebration
function triggerConfetti() {
const confettiScript = document.createElement('script');
confettiScript.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js';
confettiScript.onload = function() {
confetti({
particleCount: 200,
spread: 70,
origin: { y: 0.6 }
});
};
document.body.appendChild(confettiScript);
}
// Run the check initially when the document is ready
window.onload = function() {
checkWaniKani();
};
})();