您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically plays available audio from JapanesePod101 for Memrise's Hello 日常 course
// ==UserScript== // @name Memrise Hello 日常 Audio // @author looki // @namespace looki // @description Automatically plays available audio from JapanesePod101 for Memrise's Hello 日常 course // @include http*://*memrise.com/course/287112/*/garden/* // @include http*://*memrise.com/course/528125/*/garden/* // @version 1.0.0 // @grant none // ==/UserScript== // Dynamically created - contains the lessons, reviews, etc. var boxes = null; // Used to govern lessons/reviews var gardenObserver = null; // Cache audio files for reuse var audioMap = {}; // Main function - waits for the main DIV to be created and then observes lessons/reviews var boxesFinder = new MutationObserver(function(mutations) { boxes = document.getElementById('boxes'); if (boxes) { boxesFinder.disconnect(); gardenObserver = new MutationObserver(function(mutations) { for (var i = 0; i < mutations.length; ++i) { // A correct answer was given (hence the sparkles) if (mutations[i].target.tagName == 'IMG' && mutations[i].target.classList.contains('sparkles') && mutations[i].attributeName == 'style' && (mutations[i].oldValue == '' || mutations[i].oldValue == null)) { // Note: For some reason, oldValue is null in Chrome and empty in FireFox... playAudio(getReviewKanji(), getReviewKana()); } // A lesson page was opened else if (mutations[i].target.tagName == 'DIV' && mutations[i].target.classList.contains('garden-box') && mutations[i].target.classList.contains('presentation') && mutations[i].oldValue == 'garden-box presentation show-more choosing-mem') { playAudio(getLessonKanji(), getLessonKana()); } // The answer was correctly re-entered after a failed review else if (mutations[i].target.tagName == 'INPUT' && mutations[i].attributeName == 'class' && boxes.firstChild.classList.contains('copytyping') && mutations[i].target.classList.contains('correct') && mutations[i].target.value.trim() == getLessonKanji() ){ // Copytyping page layout == lesson layout playAudio(getLessonKanji(), getLessonKana()); } } }); gardenObserver.observe(boxes, {'attributes' : true, 'attributeOldValue' : true, 'childList' : true, 'subtree' : true}); } }); // Run the whole thing... boxesFinder.observe(document.getElementById('central-area'), {'attributes' : true}); /** * Helpers */ function getLessonKanji() { var kanji = boxes.querySelector('.row[data-column-index="1"] > .row-value'); return kanji ? kanji.innerHTML.trim() : ''; return ''; } function getLessonKana() { var kana = boxes.querySelector('.row[data-column-index="3"] > .row-value'); return kana ? kana.innerHTML.trim() : ''; } function getReviewKanji() { var correctNode = boxes.querySelector('.correct'); if (!correctNode) return ''; var kanji; // TODO: Check support for other memrise quiz types that were not encountered during development (?) if (correctNode.tagName == 'INPUT') kanji = correctNode.value; else { // Find out whether this is a JP->EN quiz or the other way around var question = boxes.querySelector('.qquestion').innerHTML; question = question.substring(0, question.indexOf('<div')); // JP->EN if (question.match(/[\u4E00-\u9FAF\u3040-\u3096\u30A1-\u30FA\uFF66-\uFF9D\u31F0-\u31FF]/)) kanji = question; // EN->JP else kanji = correctNode.querySelector('.val').innerHTML; } return kanji.trim(); } function getReviewKana() { var kana = boxes.querySelector('.extra-info'); return kana ? kana.innerHTML.trim() : ''; } /** * Search for, cache and play an audio file using the given spelling and reading */ function playAudio(kanji, kana) { if (kanji == '' || kana == '') return; var hash = kanji + '|' + kana; // Not cached yet - download and check if audio is actually available if (!audioMap[hash]) { audioMap[hash] = new Audio('http://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=' + kanji + '&kana=' + kana); audioMap[hash].playOnLoad = true; audioMap[hash].addEventListener('loadedmetadata', function() { // The 'Audio missing' clip is just over 5 seconds long if (audioMap[hash].duration < 5.0) { audioMap[hash].play(); audioMap[hash].playOnLoad = false; } else { audioMap[hash] = new Audio(); } }); } // Already cached - however, don't try to play the sound if we're still downloading it (necessary for 'missing audio' check) else if (!audioMap[hash].playOnLoad) { audioMap[hash].play(); } }