您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.
// ==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(); }; })();