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, '') }