您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Helps you answer WaniKani prompts using speech recognition
当前为
// ==UserScript== // @name WaniKani Speech Input // @namespace http://tampermonkey.net/ // @version 0.3 // @description Helps you answer WaniKani prompts using speech recognition // @author Anonymous // @match https://www.wanikani.com/subjects/* // @license MIT // @grant none // ==/UserScript== (function () { 'use strict'; const recognition = new window.webkitSpeechRecognition(); recognition.continuous = true; recognition.interimResults = true; recognition.lang = 'en-US'; let language = null; let quizData = null; let currentSubject = null; const footer = document.createElement('div'); footer.style.position = 'fixed'; footer.style.left = '0'; footer.style.bottom = '0'; footer.style.width = '100%'; footer.style.background = 'rgba(0, 0, 0, 0.5)'; footer.style.color = 'white'; footer.style.padding = '10px'; footer.style.fontSize = '36px'; footer.style.textAlign = 'center'; document.body.appendChild(footer); const quizObserver = new MutationObserver((mutationsList) => { for (let mutation of mutationsList) { if (mutation.type === 'childList') { checkAll(); } } }); async function checkAll() { const questionTypeSpan = document.querySelector('.quiz-input__question-type'); if (questionTypeSpan) { const questionType = questionTypeSpan.textContent.trim(); const newLanguage = questionType === 'reading' ? 'ja' : 'en'; if (newLanguage !== language) { language = newLanguage; console.log('Detected language: ', language); recognition.stop(); // stop recognition before changing the language } } const scriptTags = Array.from(document.getElementsByTagName('script')); const quizScript = scriptTags.find(tag => tag.getAttribute('type') === 'application/json' && tag.getAttribute('data-quiz-queue-target') === 'subjects'); if (quizScript) { quizData = JSON.parse(quizScript.textContent); } const characterDiv = document.querySelector('.character-header__characters'); if (characterDiv && quizData) { const characters = characterDiv.textContent.trim(); currentSubject = quizData.find(subject => subject.characters === characters); } } let lastCallTimestamp = 0; // Function to fetch Kanji data from API async function fetchKanjiData(kanji) { const response = await fetch(`https://kanjiapi.dev/v1/kanji/${kanji}`); const data = await response.json(); return data; } // Function to split text into individual Kanji function splitIntoKanji(text) { return Array.from(text).filter(char => (char.charCodeAt(0) >= 0x4E00 && char.charCodeAt(0) <= 0x9FFF)); } async function enterAnswerAndNext(answer) { const now = Date.now(); const minimumDelay = 3000; // Minimum delay between calls in milliseconds if (now - lastCallTimestamp < minimumDelay) { console.log('Function called too soon, ignoring.'); return; } lastCallTimestamp = now; const inputBox = document.querySelector('input.quiz-input__input[data-quiz-input-target="input"]'); inputBox.value = answer; console.log(`Answer entered: ${answer}`); const submitButton = document.querySelector('button.quiz-input__submit-button[data-quiz-input-target="button"]'); if (submitButton) { submitButton.click(); console.log('Submit button clicked'); await new Promise(resolve => { const inputContainer = document.querySelector('div[data-quiz-input-target="inputContainer"]'); let checkCount = 0; const intervalId = setInterval(() => { const correctValue = inputContainer.getAttribute('correct'); if (correctValue === 'true') { submitButton.click(); console.log('Submit button clicked again because answer was correct'); clearInterval(intervalId); resolve(); } else if (checkCount > 3) { // stop checking after ~2 seconds clearInterval(intervalId); resolve(); } checkCount++; }, 500); }); } else { console.log('Submit button not found'); } } function convertKana(input) { let hiraganaOutput = ''; let katakanaOutput = ''; for (let i = 0; i < input.length; i++) { let charCode = input.charCodeAt(i); // If the character is Hiragana, convert it to Katakana and vice versa. if (charCode >= 0x3041 && charCode <= 0x3096) { // Hiragana to Katakana: Add 96 to the charCode. katakanaOutput += String.fromCharCode(charCode + 96); hiraganaOutput += input[i]; } else if (charCode >= 0x30A1 && charCode <= 0x30F6) { // Katakana to Hiragana: Subtract 96 from the charCode. hiraganaOutput += String.fromCharCode(charCode - 96); katakanaOutput += input[i]; } else { // If the character is not Kana, just append it. hiraganaOutput += input[i]; katakanaOutput += input[i]; } } return [hiraganaOutput, katakanaOutput]; } function enterWrongAnswer(language) { const now = Date.now(); const minimumDelay = 3000; // Minimum delay between calls in milliseconds if (now - lastCallTimestamp < minimumDelay) { console.log('Function called too soon, ignoring.'); return; } lastCallTimestamp = now; var answer = "this is a wrong answer"; if (language === 'ja') { answer = "これはだめです" } const inputBox = document.querySelector('input.quiz-input__input[data-quiz-input-target="input"]'); inputBox.value = answer; console.log(`Answer entered: ${answer}`); const submitButton = document.querySelector('button.quiz-input__submit-button[data-quiz-input-target="button"]'); if (submitButton) { submitButton.click(); console.log('Submit button clicked for wrong answer'); } else { console.log('Submit button not found'); } } recognition.onresult = event => { for (let i = event.resultIndex; i < event.results.length; i++) { if (event.results[i].isFinal) { const transcript = event.results[i][0].transcript.trim().toLowerCase(); footer.textContent = `${transcript}`; console.log(`Transcript: ${transcript}`); // override to let user mark something wrong manually if(transcript === "wrong" || transcript === "ダメ") { enterWrongAnswer(language); // show the expected answer by clicking div with class additional-content__item--item-info setTimeout(() => { var infoItem = document.querySelector('.additional-content__item--item-info'); if (infoItem) { infoItem.click(); console.log('Item info clicked to show expected answer'); } else { console.log('Item info not found'); } }, 500); } // override to let user manually go to next question if(transcript === "next" || transcript === "次") { const submitButton = document.querySelector('button.quiz-input__submit-button[data-quiz-input-target="button"]'); if (submitButton) { submitButton.click(); console.log('Submit button clicked for wrong answer'); } else { console.log('Submit button not found'); } } // override to let user mark something correct manually if(transcript === "correct" || transcript === "正しい") { // get the proper answer depending on the language and quiz type // Assuming 'currentSubject' holds the correct answer var answer; if (language === 'en') { // If the language is English, we'll assume the correct answer is in 'meanings' array. // This is just an example and might need to be adjusted based on your 'currentSubject' structure answer = currentSubject.meanings[0].text; // or whichever index holds the correct answer } else if (language === 'ja') { // If the language is Japanese, we'll assume the correct answer is in 'readings' array. // This is just an example and might need to be adjusted based on your 'currentSubject' structure answer = currentSubject.readings[0].text; // or whichever index holds the correct answer } enterAnswerAndNext(answer); return; } if (!currentSubject) { console.log('Quiz subject not found. Waiting for the next question...'); return; } if (language === 'en') { const allMeanings = [ ...currentSubject.meanings.map(m => m.text.toLowerCase()) ]; console.log(`Meanings: ${allMeanings}`); for (const meaning of allMeanings) { // TODO: if meaning includes "to", make sure that we dont just match "to", make sure we match "to x" verbatim within the transcript if (transcript.includes(meaning)) { console.log(`Match found for "${transcript}" with "${meaning}"`); enterAnswerAndNext(meaning); return; } } } else if (language === 'ja') { let allReadings = []; // if it's vocab, we can just check vs the actual kanji if (currentSubject.type === 'Vocabulary') { allReadings = [...currentSubject.readings.map(r => r.text), currentSubject.characters]; console.log(`Readings: ${allReadings}`); for(const reading of allReadings){ if (transcript.includes(reading)) { console.log(`Match found for "${transcript}" with "${reading}"`); enterAnswerAndNext(allReadings[0]); return; } } // if its kanji, we need the phonetic options for all kanji in the sentance } else if (currentSubject.type === 'Kanji') { var desiredReadingType = currentSubject.primary_reading_type; const validReadings = currentSubject.readings .filter(r => r.type === desiredReadingType) .map(r => r.text); allReadings = [...validReadings]; console.log(`Readings: ${allReadings}`); for(const reading of allReadings){ if (transcript.includes(reading)) { console.log(`Match found for "${transcript}" with "${reading}"`); enterAnswerAndNext(reading); return; } } // Get individual Kanji from the transcript const kanjisInTranscript = splitIntoKanji(transcript); // Fetch data for each Kanji and check if the reading matches const kanjiDataPromises = kanjisInTranscript.map(kanji => fetchKanjiData(kanji)); Promise.all(kanjiDataPromises).then(kanjiDataArray => { for (const reading of allReadings) { for (const kanjiData of kanjiDataArray) { // Get both 'kun' and 'on' readings from the kanjiData const onKanjiReadings = kanjiData['on_readings']; const kunKanjiReadings = kanjiData['kun_readings']; // Concatenate the 'kun' and 'on' readings into one array // and convert each reading to both katakana and hiragana const kanjiReadings = [ ...onKanjiReadings.flatMap(convertKana), ...kunKanjiReadings.flatMap(convertKana) ]; for (const kanjiReading of kanjiReadings) { // Since convertKana returns an array, we should compare with kanjiReading[0] or kanjiReading[1] if (allReadings.includes(kanjiReading)) { console.log(`Match found for "${transcript}" with "${kanjiReading}"`); enterAnswerAndNext(kanjiReading); return; } } } } }); } } } } }; checkAll(); const targetNode = document.querySelector('body'); quizObserver.observe(targetNode, { childList: true, subtree: true }); recognition.continuous = true; recognition.onend = function () { recognition.lang = language === 'ja' ? 'ja-JP' : 'en-US'; recognition.start(); console.log("rec language: " + recognition.lang); } console.log("Starting language recognition"); recognition.lang = language === 'ja' ? 'ja-JP' : 'en-US'; recognition.start(); console.log("rec language: " + recognition.lang); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址