NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Duolingo Advanced Learning (w/autoupdate) // @description A must-have for advanced learners. Improves listening and writing skills by hiding the original sentence in «Translate into English» exercises — you now have to write it by ear first. // @match *://www.duolingo.com/* // @author dogewithflowers // @icon https://res.cloudinary.com/dn6n8yqqh/image/upload/c_scale,h_214/v1555635245/Icon_qqbnzf.png // @namespace duo-adv-learning-upd // @version 4.17 // @copyright 2022, vplameniraket (https://openuserjs.org/users/vplameniraket) // @license MIT // @grant GM_addStyle // @updateURL https://openuserjs.org/install/vplameniraket/Duolingo_Advanced_Learning_(wautoupdate).user.js // @downloadURL https://openuserjs.org/install/vplameniraket/Duolingo_Advanced_Learning_(wautoupdate).user.js // ==/UserScript== GM_addStyle('.textareasWrap { display: flex !important; flex-direction: row !important } .textareasWrap > * { flex-grow: 1; flex-basis: 50%; max-height: 170px; position: relative; display: block } .textareasWrap textarea { height: 100% } .targetLanguageWrap { margin-right: 15px; min-width: calc(50% - 8px); position: relative; } .secondary { opacity: .6; cursor: default; } .badge { position: absolute; z-index: 10; width: 30px; height: 30px; border-radius: 50%; text-align: center; box-sizing: border-box; padding-top: 5px; font-weight: bold; font-size: 17px; left: -13px; top: -14px; transition: all .2s ease-in-out; } .green + .badge { background-color: #78c713; color: #fff; box-shadow: 0 0 0 2px #78c713 inset; } .badge, textarea[disabled] + .badge { background-color: #f0f0f0; box-shadow: 0 0 0 2px #e5e5e5 inset; color: #c7c7c7; } .typeWhatYouHear.success { color: #5b980e; } .success+.badge { box-shadow: 0 0 0 2px #e8e8e8 inset !important; background-color: #f6f6f6 !important; } @keyframes shake { from, to { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); } 20%, 40%, 60%, 80% { transform: translateX(3px); } } .shake { animation-name: shake; } @keyframes pulse { from { transform: scale3d(1, 1, 1); } 50% { transform: scale3d(1.03, 1.03, 1.03); } to { transform: scale3d(1, 1, 1); } } .pulse { animation-name: pulse } .animated { animation-duration: 1s; animation-fill-mode: both; } .animated.fast { animation-duration: 800ms; } .animated.faster { animation-duration: 500ms; } @media screen and (max-width: 699px) {.badge { display: none } .targetLanguageWrap { margin-right: 0; min-width: auto} .textareasWrap { display: block }}'); var v = '4.15', news = ['Duolingo Advanced Learning\n', '\n' + v + ' — What is new?\n', '— Minor adjustments.' ]; if (!localStorage.getItem('duoadv_v') || localStorage.getItem('duoadv_v') != v) { localStorage.setItem('duoadv_v', v); alert(news.join('')); } var keyboardDefined = localStorage.getItem('keyboard') ? true : false, headerSelector = 'h1[data-test="challenge-header"] span', nativeLanguageTextareaSelector = '[data-test="challenge-translate-input"]:not([autocorrect]):not([spellcheck]):not(.typeWhatYouHear)', challengeContainerSelector = '[data-test="challenge challenge-translate"]', toggleInputMethodSelector = '[data-test="player-toggle-keyboard"]', sentenceContainerSelector = challengeContainerSelector + ' [dir="ltr"] > span + span', audioButtonSelector = challengeContainerSelector + ' span > button', challengeContainer, sentenceContainer, nativeLanguageWrap, nativeLanguageTextarea, targetLanguageWrap, targetLanguageTextarea, nativeLanguageTextareaIsFound = false, targetSentence, targetSentenceRevealed; GM_addStyle(sentenceContainerSelector + ':not(:hover):not(.reveal) {filter: blur(4px)}' + sentenceContainerSelector + '{filter: blur(0px); transition: all .2s ease-in-out}'); setInterval(function(){ if (!nativeLanguageTextareaIsFound) { nativeLanguageTextarea = document.querySelectorAll(challengeContainerSelector + ' ' + nativeLanguageTextareaSelector)[0]; if (document.body.contains(nativeLanguageTextarea)) { nativeLanguageTextareaIsFound = true; var correct = false; challengeContainer = document.querySelectorAll(challengeContainerSelector)[0]; challengeContainer.classList.add('challengeContainer'); nativeLanguageWrap = nativeLanguageTextarea.parentNode; if (!challengeContainer.classList.contains('processed')) { challengeContainer.classList.add('processed'); var header = challengeContainer.querySelectorAll(headerSelector)[0]; if (header.innerText == 'Write this in English') { header.innerText = 'Write down and translate what you hear'; } nativeLanguageTextarea.parentNode.insertAdjacentHTML('beforebegin','<div class="targetLanguageWrap animated fast"><textarea data-test="challenge-translate-input" class="' + nativeLanguageTextarea.getAttribute('class') + ' typeWhatYouHear" dir="ltr" placeholder="Type what you hear" style="height: 100%"></textarea><div class="badge">1</div><audio class="successSound"><source src="https://res.cloudinary.com/dn6n8yqqh/video/upload/v1559766656/DuolingoSuccessMP3.mp3" type="audio/mpeg"><source src="https://res.cloudinary.com/dn6n8yqqh/video/upload/v1555571639/DuolingoSuccessOGG.ogg" type="audio/ogg"><source src="https://res.cloudinary.com/dn6n8yqqh/video/upload/v1555571640/DuolingoSuccessWAV.wav" type="audio/wav"></audio></div>'); nativeLanguageTextarea.insertAdjacentHTML('afterend','<div class="badge">2</div>'); targetLanguageWrap = challengeContainer.querySelectorAll('.targetLanguageWrap')[0]; targetLanguageWrap.parentNode.classList.add('textareasWrap'); targetLanguageTextarea = targetLanguageWrap.querySelectorAll('.typeWhatYouHear')[0]; targetLanguageTextarea.classList.add('green'); targetLanguageTextarea.focus(); nativeLanguageTextarea.classList.add('secondary'); nativeLanguageTextarea.disabled = true; sentenceContainer = challengeContainer.querySelectorAll(sentenceContainerSelector)[0]; targetSentenceRevealed = false; targetSentence = sentenceContainer.innerText.toLowerCase(); sentenceContainer.classList.add('sentence'); if (!challengeContainer.classList.contains('events-added')) { challengeContainer.classList.add('events-added'); challengeContainer.addEventListener('keydown', function (e) { switch (e.keyCode) { case 17: document.querySelectorAll(audioButtonSelector)[0].click(); break; case 9: e.preventDefault(); if (targetSentenceRevealed == false) { sentenceContainer.classList.add('reveal'); challengeContainer.classList.add('reveal'); targetSentenceRevealed = true; } return; break; } }); challengeContainer.addEventListener('keyup', function (e) { if (e.keyCode === 9) { e.preventDefault(); sentenceContainer.classList.remove('reveal'); challengeContainer.classList.remove('reveal'); targetSentenceRevealed = false; } }); } targetLanguageTextarea.addEventListener('keydown', function (e) { switch (e.keyCode) { case 13: validate(e); break; case 27: nativeLanguageTextarea.disabled = false; nativeLanguageTextarea.classList.replace('secondary','green'); nativeLanguageTextarea.focus(); targetLanguageTextarea.disabled = true; targetLanguageTextarea.classList.add('secondary'); toggleTargetKeyboard('disabled'); toggleNativeKeyboard('enabled'); break; } }); nativeLanguageTextarea.addEventListener('keydown', function (e) { if (e.keyCode === 27 && !correct) { nativeLanguageTextarea.disabled = true; nativeLanguageTextarea.classList.replace('green','secondary'); targetLanguageTextarea.disabled = false; targetLanguageTextarea.classList.remove('secondary'); targetLanguageTextarea.focus(); toggleTargetKeyboard('enabled'); toggleNativeKeyboard('disabled'); } }); function validate(e) { var text = e.target.value.replace(/[ ]$/g, '').replace(/^[ ]/g, '').replace(/[/:]/g, '').replace(/[ ][ ]/g, ' ').toLowerCase(), noCommasAndColons = targetSentence.replace(/[,][ ]/g, ' ').replace(/[、]/g, '').replace(/[:]/g, ''), filteredSentence = removeDotAndExcMarks(targetSentence), filterednoCommasAndColons = removeDotAndExcMarks(noCommasAndColons), everyPossibleSentenceVariation = [targetSentence, filteredSentence, replaceNonLatin(targetSentence), normalize(targetSentence), normalize(replaceNonLatin(targetSentence)), replaceNonLatin(filteredSentence), normalize(filteredSentence), normalize(replaceNonLatin(filteredSentence)), noCommasAndColons, filterednoCommasAndColons, replaceNonLatin(noCommasAndColons), normalize(noCommasAndColons), normalize(replaceNonLatin(noCommasAndColons)), replaceNonLatin(filterednoCommasAndColons), normalize(filterednoCommasAndColons), normalize(replaceNonLatin(filterednoCommasAndColons))]; if (everyPossibleSentenceVariation.includes(text)) { nativeLanguageTextarea.classList.replace('secondary','green'); nativeLanguageTextarea.disabled = false; nativeLanguageTextarea.focus(); targetLanguageTextarea.disabled = true; targetLanguageTextarea.classList.add('secondary','success'); targetLanguageWrap.classList.replace('fast','faster'); targetLanguageWrap.classList.add('pulse'); var player = targetLanguageWrap.querySelectorAll('.successSound')[0]; player.volume = 0.3; player.play(); toggleTargetKeyboard('disabled'); toggleNativeKeyboard('enabled'); correct = true; } else { targetLanguageWrap.classList.remove('shake'); targetLanguageWrap.classList.add('shake'); setTimeout(function(){ targetLanguageWrap.classList.remove('shake'); },800); } } if (keyboardDefined && localStorage.getItem('keyboard') !== '') { targetLanguageWrap.insertAdjacentHTML('beforeend', localStorage.getItem('keyboard')); targetLanguageWrap.querySelectorAll('audio + div')[0].classList.add('keyboard'); var keybuttons = targetLanguageWrap.querySelectorAll('textarea ~ .keyboard button'); function lowerCase() { keybuttons[0].innerText = '↑'; keybuttons.forEach(function(keybutton){ keybutton.innerText = keybutton.innerText.toLowerCase(); }); } function upperCase() { keybuttons[0].innerText = '↓'; keybuttons.forEach(function(keybutton){ keybutton.innerText = keybutton.innerText.toUpperCase(); }); } if (keybuttons[0].innerText == '↓') { lowerCase(); } keybuttons.forEach(function(keybutton, i){ keybutton.removeAttribute('disabled'); keybutton.onclick = !i ? function(){ this.innerText == '↓' ? lowerCase() : upperCase(); } : function(){ insertAtCursor(targetLanguageTextarea, this.innerText); targetLanguageTextarea.focus(); } }); } var toggleInputMethodButton = document.querySelectorAll(toggleInputMethodSelector)[0]; if (!!toggleInputMethodButton) { toggleInputMethodButton.onclick = function(){ if (!!targetLanguageWrap) { targetLanguageWrap.remove(); } } } var targetKeyboardButtons = targetLanguageWrap.querySelectorAll('textarea ~ div button'); function toggleTargetKeyboard(state) { if (!!targetKeyboardButtons) { targetKeyboardButtons.forEach(function(keybutton){ keybutton.disabled = state == 'disabled'; }); } } var nativeKeyboardButtons = nativeLanguageWrap.querySelectorAll('textarea ~ div button'); function toggleNativeKeyboard(state) { if (!!nativeKeyboardButtons) { nativeKeyboardButtons.forEach(function(keybutton){ keybutton.disabled = state == 'disabled'; }); } } toggleTargetKeyboard('enabled'); toggleNativeKeyboard('disabled'); } } else { if (document.body.contains(targetLanguageWrap)) { targetLanguageWrap.remove(); } if (document.body.contains(challengeContainer)) { challengeContainer.classList.remove('processed'); } nativeLanguageTextareaIsFound = false; } } else if (!document.body.contains(nativeLanguageTextarea)) { nativeLanguageTextareaIsFound = false; } if (!!document.querySelectorAll('textarea[autocorrect][spellcheck]')[0]) { var keyboard = document.querySelectorAll('textarea[autocorrect][spellcheck] + div')[0]; if (!!keyboard) { if (!keyboardDefined || keyboard.outerHTML !== localStorage.getItem('keyboard')) { localStorage.setItem('keyboard', keyboard.outerHTML); } } else { localStorage.setItem('keyboard', ''); } } }, 50); function insertAtCursor(field, val) { if (document.selection) { field.focus(); var sel = document.selection.createRange(); sel.text = val; } else if (field.selectionStart || field.selectionStart == '0') { var start = field.selectionStart, end = field.selectionEnd; field.value = field.value.substring(0, start) + val + field.value.substring(end, field.value.length); field.selectionStart = start + val.length; field.selectionEnd = start + val.length; } else field.value += val; } function removeDotAndExcMarks(string) { return string.replace(/[ ][.?!]$/g, '').replace(/[.?!]$/g, '').replace(/[¿]/g, '').replace(/[¡]/g, '').replace(/[。!?]$/g, '') } function replaceNonLatin(string) { return string.replace(/[æ]/g, 'ae').replace(/[ø]/g, 'oe').replace(/[å]/g, 'aa').replace(/[ß]/g, 'ss').replace(/[œ]/g, 'oe').replace(/[ñ]/g, 'n\'').replace(/[dž]/g, 'dzh').replace(/[ĉ]/g, 'cx').replace(/[ĝ]/g, 'gx') } function normalize(string) { return string.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[\u0591-\u05C7]/g, '') }