// ==UserScript==
// @name Duolingo HearEverything
// @namespace http://tampermonkey.net/
// @version 0.69.1
// @description Reads aloud most sentences in Duo's challenges.
// @author Esh
// @match https://*.duolingo.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @downloadURL https://update.greasyfork.icu/scripts/425694/Duolingo%20HearEverything.user.js
// @updateURL https://update.greasyfork.icu/scripts/425694/Duolingo%20HearEverything.meta.js
// ==/UserScript==
const VERSION = '0.69.1 --- 1 ---';
const LOG_STRING = 'Duolingo HearEverything: ';
let voiceSelect;
const config = {};
const DEBUG = false;
// for config mouse hover
let hover = true;
const synth = window.speechSynthesis;
let voices = [];
const challengesUrls = [];
const challengesReads = [];
let timeout;
let howlPlay = false;
const page = {};
page.isNewPage = false;
page.isOptionSpeechAdded = false;
const speakerButton = `
`;
// Element definitions
const DIALOGUE_SPEAKER_CLASS = '_29e-M _39MJv _2Hg6H';
// currently used
const ANSWER_CLASS = '._1UqAr';
const ANSWER = 'blame';
const ANSWER_QS = dataTestContains(ANSWER);
const RIGHT_ANSWER = 'blame-correct';
const RIGHT_ANSWER_QS = dataTestContains(RIGHT_ANSWER);
const RIGHT_ANSWER_TYPO_QS = '._3gI0Y';
const WRONG_ANSWER = 'blame-incorrect';
const WRONG_ANSWER_QS = dataTestContains(WRONG_ANSWER);
const CHALLENGE_TAP_TOKEN = 'challenge-tap-token'; // challenge-translate (tap)
const CHALLENGE_TAP_TOKEN_QS = dataTestIs(CHALLENGE_TAP_TOKEN);
const CHALLENGE_TAP_TOKEN_TEXT = 'challenge-tap-token-text';
const CHALLENGE_TAP_TOKEN_TEXT_QS = dataTestIs(CHALLENGE_TAP_TOKEN_TEXT);
const WORD_BANK = 'word-bank'; // if exists it's tap instead of keyboard (challenge-translate)
const WORD_BANK_QS = dataTestIs(WORD_BANK);
const TRANSLATE_INPUT = 'challenge-translate-input';
const TRANSLATE_INPUT_QS = dataTestIs(TRANSLATE_INPUT);
const SPEAKER_BUTTON = '._1KXUd._1I13x._2kfEr._1nlVc._2fOC9.UCrz7.t5wFJ';
const SPEAKER_BUTTON_QS = SPEAKER_BUTTON;
const HINT_SENTENCE = '._29e-M._39MJv._2Hg6H';
const HINT_SENTENCE_QS = HINT_SENTENCE;
const CHALLENGE_JUDGE_TEXT = 'challenge-judge-text';
const CHALLENGE_JUDGE_TEXT_QS = dataTestIs(CHALLENGE_JUDGE_TEXT);
const CHALLENGE_JUDGE_INTRO = 'hint-token';
const CHALLENGE_JUDGE_INTRO_QS = dataTestIs(CHALLENGE_JUDGE_INTRO);
const FORM_PROMPT = '._2SfAl._2Hg6H';
const FORM_PROMPT_QS = FORM_PROMPT;
const RIGHT_OPTION_QS = '[aria-checked="true"] div';
const TEXT_INPUT = 'challenge-text-input';
const TEXT_INPUT_QS = dataTestIs(TEXT_INPUT);
const SPEAK_INTRO = 'speakIntro';
const SPEAK_INTRO_QS = '#' + SPEAK_INTRO;
const HINT_TOKEN = 'hint-token';
const HINT_TOKEN_QS = dataTestIs(HINT_TOKEN);
const GAP_FILL_UNDERSCORE_QS = '._2Iqyl';
const TIP_TEXT_QS = '._1WCLL';
// used page types
const COMPLETE_REVERSE_TRANSLATION = 'challenge-completeReverseTranslation';
const DIALOGUE = 'challenge-dialogue';
const FORM = 'challenge-form';
const GAP_FILL = 'challenge-gapFill';
const LISTEN = 'challenge-listen';
const LISTEN_COMPREHENSION = 'challenge-listenComprehension';
const LISTEN_TAP = 'challenge-listenTap';
const MATCH = 'challenge-match';
const NAME = 'challenge-name';
const READ_COMPREHENSION = 'challenge-readComprehension';
const SPEAK = 'challenge-speak';
const TAP_COMPLETE = 'challenge-tapComplete';
const TAP_CLOZE = 'challenge-tapCloze';
const TAP_CLOZE_TABLE = 'challenge-tapClozeTable';
const TRANSLATE = 'challenge-translate';
const TYPE_CLOZE = 'challenge-typeCloze';
const TIP = 'tip';
// Print nice debug statements
function debug (s) {
const name = (debug.caller !== null) ? debug.caller.name : '';
if (typeof (s) === 'object') {
console.debug(LOG_STRING + ' ' + name + '(): ');
console.debug(s);
} else {
console.debug(LOG_STRING + ' ' + name + '(): ' + s);
}
}
function dataTestContains (token) {
const result = '[data-test~="' + token + '"]';
return result;
}
function dataTestIs (token) {
return '[data-test="' + token + '"]';
}
// needed for Duo Mute
// intercept xmlhttprequest to get session json and extract challenges
(function (open) {
XMLHttpRequest.prototype.open = function () {
this.addEventListener('readystatechange', function () {
if (this.readyState === 4 && this.responseURL.includes('sessions')) {
if (this.response.challenges) {
debug(this.response.challenges);
this.response.challenges.forEach((rspChallenge) => {
if (typeof rspChallenge.tts !== 'undefined') {
challengesUrls.push(rspChallenge.tts);
challengesReads.push(rspChallenge.prompt);
}
});
}
}
}, false);
open.apply(this, arguments);
};
})(XMLHttpRequest.prototype.open);
// needed for Duo Muto
// Intercept Howl.play (which plays the sound over Howl.js).
// of course the linter doesn't know the object Howl, but it is there ;)
(function (play) {
// eslint-disable-next-line no-undef
Howl.prototype.play = function () {
// if we read the options, Duo has to remain silent
if (!page.isReadingOptions) {
// if Duo is muted, we have to do his job
if (config.he_muteduo) {
const read = challengesReads[challengesUrls.indexOf(this._src)];
if (DEBUG) document.querySelector('#mySentence').innerText = read;
if (typeof read !== 'undefined') {
debug('intercepting Duo speaking = ' + read);
howlPlay = true;
clearTimeout(timeout);
const utter = generateUtter(read);
synth.cancel();
synth.speak(utter);
} else {
play.apply(this, arguments);
}
} else {
play.apply(this, arguments);
}
} else {
// We spoke it, so we can let Duo take over
page.isReadingOptions = false;
debug('Shhh Duo! I am reading the options');
}
};
// eslint-disable-next-line no-undef
})(Howl.prototype.play);
window.addEventListener('load', function () {
'use strict';
debug(VERSION);
voices = window.speechSynthesis.getVoices();
readConfig();
new MutationObserver(start).observe(document.body, {
childList: true,
subtree: true
});
debug('MutationObserver running');
});
// toggles visibility
function togglePopout (id) {
const popout = document.getElementById(id);
if (popout.style.display === 'none') {
popout.style.display = 'block';
document.addEventListener('click', closePopout);
popout.addEventListener('mouseenter', setHover);
popout.addEventListener('mouseleave', removeHover);
} else {
popout.style.display = 'none';
document.removeEventListener('click', closePopout);
popout.removeEventListener('mouseenter', setHover);
popout.removeEventListener('mouseleave', removeHover);
}
function setHover () {
hover = true;
}
function removeHover () {
setTimeout(function () { hover = false; }, '100');
}
function closePopout () {
if (!hover) {
hover = true;
togglePopout('hearEverythingConfig');
}
}
}
// gets the stored config
function readConfig () {
// eslint-disable-next-line no-undef
voiceSelect = GM_getValue('voiceSelect', 1000);
setVoice();
config.ap_timeout = 1000;
// eslint-disable-next-line no-undef
config.he_muteduo = GM_getValue('he_muteduo', false);
configChallenge(DIALOGUE, 'cd', false, true, true);
configChallenge(FORM, 'cf', true, true, false);
configChallenge(GAP_FILL, 'cgf', true, true, true);
configChallenge(LISTEN, 'cl', true, null, null);
configChallenge(LISTEN_COMPREHENSION, 'clc', null, true, null);
configChallenge(LISTEN_TAP, 'clt', true, true, null);
configChallenge(MATCH, 'cm', null, true, null);
configChallenge(NAME, 'cn', true, null, null);
configChallenge(READ_COMPREHENSION, 'crc', true, false, false);
configChallenge(SPEAK, 'cs', true, null, null);
configChallenge(TAP_CLOZE, 'ctcl', true, null, false);
configChallenge(TAP_CLOZE_TABLE, 'ctct', false, null, true);
configChallenge(TAP_COMPLETE, 'ctc', true, false, true);
configChallenge(TRANSLATE, 'ct', true, true, null);
configChallenge(TYPE_CLOZE, 'ctyc', true, null, false);
configChallenge(TIP, 't', false, false, false);
// auto/click/autointro default: true/false, if not used: null
function configChallenge (_challengeName, shortName, auto, click, autointro) {
const keyAuto = 'he_' + shortName + '_auto';
const keyClick = 'he_' + shortName + '_click';
const keyAutointro = 'he_' + shortName + '_autointro';
// eslint-disable-next-line no-undef
if (auto !== null) config[keyAuto] = GM_getValue(keyAuto, auto);
// eslint-disable-next-line no-undef
if (click !== null) config[keyClick] = GM_getValue(keyClick, click);
// eslint-disable-next-line no-undef
if (autointro !== null) config[keyAutointro] = GM_getValue(keyAutointro, autointro);
}
function setVoice () {
const duoState = JSON.parse(localStorage.getItem('duo.state'));
config.lang = duoState.user.learningLanguage;
if (voiceSelect === 1000) {
for (let i = 0; i < voices.length; i++) {
if (voices[i].lang.includes(config.lang)) {
voiceSelect = i;
}
}
}
}
}
// adds config to the page
function addConfig () {
const nameWidth = '29ch';
if (!document.querySelector('#hearEverythingGear') && document.querySelector('[role="progressbar"]')) {
const configButton = document.createElement('button');
configButton.setAttribute('id', 'hearEverythingGear');
configButton.setAttribute('class', '_2hiHn _2kfEr _1nlVc _2fOC9 UCrz7 t5wFJ _1DC8p _2jNpf');
configButton.setAttribute('style', `grid-column: 3/3; background-image:url(//d35aaqx5ub95lt.cloudfront.net/images/gear.svg);
background-position: 0px 0px; background-repeat: no-repeat; background-size: contain;`);
const configDiv = document.createElement('div');
configDiv.setAttribute('class', '_3yqw1 np6Tv _1Xlh1');
configDiv.setAttribute('style', 'display: none; position: fixed; margin-top: 1rem;');
configDiv.setAttribute('id', 'hearEverythingConfig');
let options = '';
for (let i = 0; i < voices.length; i++) {
options += ``;
}
const styleCheckbox = 'style="vertical-align: bottom;"';
const configMute = `
`;
// autoplay, play options, read intro
const configDialogue = createConfigOption(DIALOGUE, 'cd', true, true, true);
const configForm = createConfigOption(FORM, 'cf', true, true, true);
const configGapFill = createConfigOption(GAP_FILL, 'cgf', true, true, true);
const configListen = createConfigOption(LISTEN, 'cl', true, false, false);
const configListenComprehension = createConfigOption(LISTEN_COMPREHENSION, 'clc', false, true, false);
const configListenTap = createConfigOption(LISTEN_TAP, 'clt', true, true, false);
const configMatch = createConfigOption(MATCH, 'cm', false, true, false);
const configName = createConfigOption(NAME, 'cn', true, false, false);
const configReadComprehension = createConfigOption(READ_COMPREHENSION, 'crc', true, true, true);
const configSpeak = createConfigOption(SPEAK, 'cs', true, false, false);
const configTapCloze = createConfigOption(TAP_CLOZE, 'ctcl', true, false, true);
const configTapClozeTable = createConfigOption(TAP_CLOZE_TABLE, 'ctct', true, false, true);
const configTapComplete = createConfigOption(TAP_COMPLETE, 'ctc', true, true, true);
const configTranslate = createConfigOption(TRANSLATE, 'ct', true, true, false);
const configTypeCloze = createConfigOption(TYPE_CLOZE, 'ctyc', true, false, false);
configDiv.innerHTML = `
${configDialogue}
${configForm}
${configGapFill}
${configListen}
${configListenComprehension}
${configListenTap}
${configMatch}
${configName}
${configReadComprehension}
${configSpeak}
${configTapCloze}
${configTapClozeTable}
${configTapComplete}
${configTranslate}
${configTypeCloze}
${configMute}
`;
document.querySelector('[role="progressbar"]').insertAdjacentElement('afterend', configButton);
configButton.insertAdjacentElement('afterend', configDiv);
configButton.addEventListener('click', function () { togglePopout('hearEverythingConfig'); });
const configLanguage = document.getElementById('configLanguage');
configLanguage.querySelector('[value="' + voiceSelect + '"]').setAttribute('selected', true);
configLanguage.addEventListener('change', function () {
voiceSelect = configLanguage.options[configLanguage.selectedIndex].value;
// eslint-disable-next-line no-undef
GM_setValue('voiceSelect', voiceSelect);
});
setVisibleConfig();
document.getElementById('hearEverythingConfig').addEventListener('change', function (e) {
// eslint-disable-next-line no-undef
GM_setValue(e.target.id, e.target.checked);
config[e.target.id] = e.target.checked;
});
}
if (document.querySelector('#hearEverythingGear') && document.querySelector('[role="progressbar"]')) {
highlightConfig();
}
// builds a configBlock
// auto = autoplay, click = read options, intro = read intro
function createConfigOption (challengeName, prefix, auto, click, intro) {
const nameArr = challengeName.split('-');
if (nameArr.length === 1) {
nameArr[1] = nameArr[0];
}
const name1Arr = nameArr[1].match(/[a-z]+|[A-Z][a-z]+/g);
nameArr[1] = name1Arr.join(' ');
for (let i = 0; i < nameArr.length; i++) {
nameArr[i] = nameArr[i][0].toUpperCase() + nameArr[i].substr(1);
}
const name = nameArr.join(' ');
const styleCheckbox = 'style="vertical-align: bottom;"';
let clickSpan = '';
let autoSpan = '';
let introSpan = '';
if (auto === true) autoSpan = `
`;
if (click === true) clickSpan = `
`;
if (intro === true) introSpan = `
`;
return `
${name}:
${autoSpan}
${clickSpan}
${introSpan}
`;
}
// sets all checkboxes to the current config
function setVisibleConfig () {
(Object.keys(config)).forEach((key) => {
if (document.getElementById(key)) document.getElementById(key).checked = config[key];
});
}
// highlights the current challenge config
// list = array
function highlightConfig () {
const challenge = (page.challenge === COMPLETE_REVERSE_TRANSLATION) ? TRANSLATE : page.challenge;
const allConfigs = document.querySelectorAll('.myConfig');
allConfigs.forEach((entry) => {
entry.style = 'border: none; padding: 3px;';
});
const element = document.querySelector('#config-' + challenge);
if (element !== null) element.style = 'border: 1px solid gray; border-radius: 2px; padding: 3px;';
}
}
// start whenever the mutation observer wants you to start
function start () {
if ((window.location.pathname.includes('/skill')) && (document.querySelector('[data-test="challenge-header"]') !== null || document.querySelector(CHALLENGE_JUDGE_TEXT) !== null)) {
checkNewPage();
if (page.challenge) {
addConfig();
buildDebug();
if (page.isNewPage) {
setupPageInformation();
setupAllChallenges();
resetPageAtVisibleAnswer();
}
} else {
page.isNewPage = false;
}
}
function setupPageInformation () {
page.isAnswerVisible = (document.querySelector(ANSWER_QS) !== null);
page.hasIntroSpeakerButton = (document.querySelector(SPEAK_INTRO_QS) !== null);
page.hasSpeakerButton = (document.querySelector(SPEAKER_BUTTON_QS) !== null);
page.isWrongAnswer = (document.querySelector(WRONG_ANSWER_QS) !== null);
page.isRightAnswer = (document.querySelector(RIGHT_ANSWER_QS) !== null);
page.isRightAnswerTypo = (document.querySelector(RIGHT_ANSWER_TYPO_QS) !== null);
}
function setupAllChallenges () {
if (page.challenge === COMPLETE_REVERSE_TRANSLATION) setupCompleteReverseTranslation();
if (page.challenge === DIALOGUE) setupDialogue();
if (page.challenge === FORM) setupForm();
if (page.challenge === GAP_FILL) setupGapFill();
if (page.challenge === MATCH) setupMatch();
if (page.challenge === NAME) setupName();
if (page.challenge === LISTEN_COMPREHENSION) setupListenComprehension();
if (page.challenge === LISTEN_TAP) setupListenTap();
if (page.challenge === READ_COMPREHENSION) setupReadComprehension();
if (page.challenge === SPEAK) setupSpeak();
if (page.challenge === TAP_CLOZE) setupTapCloze();
if (page.challenge === TAP_CLOZE_TABLE) setupTapClozeTable();
if (page.challenge === TAP_COMPLETE) setupTapComplete();
if (page.challenge === (TRANSLATE || LISTEN)) setupTranslate();
if (page.challenge === TYPE_CLOZE) setupTypeCloze();
if (!page.challenge && document.querySelector(CHALLENGE_JUDGE_TEXT)) setupTip();
}
function resetPageAtVisibleAnswer () {
if (page.isAnswerVisible) {
// Alt + l for Duo or our speaker button
document.removeEventListener('keydown', myShortcutListener);
document.addEventListener('keydown', myShortcutListener);
// reset page to allow new processing
page.isNewPage = false;
page.isOptionSpeechAdded = false;
}
}
}
function setupCompleteReverseTranslation () {
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeCompleteReverseTranslation(), config.he_ct_auto);
function prepareChallengeCompleteReverseTranslation () {
let read;
if (page.isRightAnswer) {
const tiParent = document.querySelector(TEXT_INPUT_QS).parentNode;
tiParent.innerText = document.querySelector(TEXT_INPUT_QS).value;
read = tiParent.parentNode.innerText;
}
if (page.isWrongAnswer || page.isRightAnswerTypo) {
const answer = document.querySelector(ANSWER_CLASS);
if (answer.lastElementChild) {
read = answer.lastElementChild.innerText;
} else {
read = answer.innerText;
}
}
if (page.hasSpeakerButton) {
read = document.querySelector(HINT_TOKEN_QS).parentNode.innerText;
}
return read;
}
}
function setupDialogue () {
if (!page.hasIntroSpeakerButton) handleIntroReading(introChallengeDialogue(), config.he_cd_autointro);
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeDialogue(), config.he_cd_auto);
if (!page.isOptionSpeechAdded && document.querySelectorAll(CHALLENGE_JUDGE_INTRO_QS).length !== 0 && config.he_cd_click) {
addSpeech(CHALLENGE_JUDGE_TEXT_QS);
}
function prepareChallengeDialogue () {
// TODO: remove hard coded references
const speaker1 = document.querySelector('[class="' + DIALOGUE_SPEAKER_CLASS + '"]').innerText;
let speaker2;
if (page.isWrongAnswer) {
speaker2 = document.querySelector('._1UqAr._1sqiF').innerText;
} else {
speaker2 = document.querySelector('[aria-checked="true"]').querySelector('[data-test="challenge-judge-text"]').innerText;
}
return speaker1 + '\n' + speaker2;
}
function introChallengeDialogue () {
if (document.querySelector(HINT_SENTENCE_QS)) {
const read = document.querySelector(HINT_SENTENCE_QS).innerText;
const speaker = document.createElement('div');
speaker.innerHTML = speakerButton;
speaker.children[0].id = SPEAK_INTRO;
speaker.children[0].style = 'width:40px; height:40px; background:transparent; margin-left:-16px; margin-right:0px; padding-bottom:5px';
document.querySelector(HINT_SENTENCE_QS).insertAdjacentElement('afterBegin', speaker);
return read;
}
}
}
function setupForm () {
if (!page.hasIntroSpeakerButton) handleIntroReading(introChallengeForm(), config.he_cf_autointro);
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeForm(), config.he_cf_auto);
if (page.isOptionSpeechAdded === false && document.querySelectorAll(CHALLENGE_JUDGE_TEXT_QS).length !== 0 && config.he_cf_click === true) {
addSpeech(CHALLENGE_JUDGE_TEXT_QS);
}
function prepareChallengeForm () {
let answer;
if (page.isRightAnswer) {
answer = document.querySelector(RIGHT_OPTION_QS).innerText;
document.querySelector(GAP_FILL_UNDERSCORE_QS).innerHTML = hintTokenSpan(answer);
}
if (page.isWrongAnswer) {
const answerElement = document.querySelector(ANSWER_CLASS);
if (answerElement.lastElementChild) {
answer = answerElement.lastElementChild.innerText;
} else {
answer = answerElement.innerText;
}
}
const read = document.querySelector(FORM_PROMPT_QS).getAttribute('data-prompt').replace(/_+/, answer);
if (page.isWrongAnswer) document.querySelector(FORM_PROMPT_QS).innerHTML = `
${read}`;
return read;
}
function introChallengeForm () {
if (document.querySelector(FORM_PROMPT_QS)) {
const read = document.querySelector(FORM_PROMPT_QS).getAttribute('data-prompt').replace(/_+/, '\n');
const speaker = document.createElement('div');
speaker.innerHTML = speakerButton;
speaker.children[0].id = SPEAK_INTRO;
speaker.children[0].style = 'width:40px; height:40px; background:transparent; margin-left:-16px; margin-right:0px; padding-bottom:5px';
document.querySelector(FORM_PROMPT_QS).insertAdjacentElement('afterBegin', speaker);
return read;
}
}
}
function setupGapFill () {
if (!page.hasIntroSpeakerButton) handleIntroReading(introChallengeGapFill(), config.he_cgf_autointro);
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeGapFill(), config.he_cgf_auto);
if (!page.isOptionSpeechAdded && document.querySelectorAll(CHALLENGE_JUDGE_INTRO_QS).length !== 0 && config.he_cgf_click === true) {
addSpeech(CHALLENGE_JUDGE_TEXT_QS);
}
function prepareChallengeGapFill () {
let answer;
if (page.isRightAnswer) {
answer = document.querySelector(RIGHT_OPTION_QS).innerText;
}
if (page.isWrongAnswer) {
const answerElement = document.querySelector(ANSWER_CLASS);
if (answerElement.lastElementChild) {
answer = answerElement.lastElementChild.innerText;
} else {
answer = answerElement.innerText;
}
}
// question
let read = document.querySelector(HINT_TOKEN_QS).parentNode.parentNode.innerText;
// new type, which has two blanc places
if (answer.includes('...')) {
const answers = answer.split(' ... ');
debug('answer 1 = ' + answers[0]);
debug('answer 2 = ' + answers[1]);
const reads = read.split('\n');
debug('reads = ' + reads);
if (reads.length === 2) {
read = answers[0] + reads[0] + answers[1] + reads[1];
} else {
read = reads[0] + answers[0] + reads[1] + answers[1] + reads[2];
}
const underscores = document.querySelectorAll(GAP_FILL_UNDERSCORE_QS);
underscores.forEach(function (underscore, index) {
underscore.innerHTML = hintTokenSpan(answers[index]);
});
} else {
// if the answer is at the start of the sentence, there's no \n
if (read.includes('\n')) {
read = read.replace('\n', answer);
} else {
read = answer + ' ' + read;
}
document.querySelector(GAP_FILL_UNDERSCORE_QS).innerHTML = hintTokenSpan(answer);
}
return read;
}
}
function setupListenComprehension () {
if (page.isOptionSpeechAdded === false && document.querySelectorAll(CHALLENGE_JUDGE_INTRO_QS).length !== 0 && config.he_clc_click === true) {
const hint = document.querySelector(HINT_TOKEN_QS).parentNode.innerText.replace('…', '').replace('...', '');
addSpeech(CHALLENGE_JUDGE_TEXT_QS, hint);
}
}
function setupListenTap () {
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeListenTap(), config.he_clt_auto);
if (!page.isOptionSpeechAdded && config.he_clt_click && document.querySelectorAll(CHALLENGE_TAP_TOKEN_QS).length !== 0) {
addSpeech(CHALLENGE_TAP_TOKEN_QS, '', true);
}
function prepareChallengeListenTap () {
if (page.isWrongAnswer) {
const answer = document.querySelector(ANSWER_CLASS);
if (answer.lastElementChild) {
return answer.lastElementChild.innerText;
} else {
return answer.innerText;
}
}
if (page.isRightAnswer) {
if (document.querySelector(CHALLENGE_TAP_TOKEN_QS)) {
const read = document.querySelector(CHALLENGE_TAP_TOKEN_QS).parentNode.parentNode.innerText;
return read.replace(/\n/g, ' ').replace(/' /g, "'");
}
}
}
}
function setupMatch () {
if (!page.isOptionSpeechAdded && config.he_cm_click && document.querySelectorAll(CHALLENGE_TAP_TOKEN_TEXT_QS).length !== 0) {
addSpeech(CHALLENGE_TAP_TOKEN_TEXT_QS, '', true);
}
}
function setupName () {
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeName(), config.he_cn_auto);
function prepareChallengeName () {
let read;
if (page.isRightAnswer) {
read = document.querySelector(TEXT_INPUT_QS).value;
}
if (page.isWrongAnswer) {
read = document.querySelector(ANSWER_CLASS).innerText;
}
return read;
}
}
function setupReadComprehension () {
if (!page.hasIntroSpeakerButton) handleIntroReading(introChallengeReadComprehension(), config.he_crc_autointro);
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeReadComprehension(), config.he_crc_auto);
if (!page.isOptionSpeechAdded && config.he_crc_click && document.querySelectorAll(CHALLENGE_JUDGE_TEXT_QS).length !== 0) {
const hint = document.querySelector(HINT_TOKEN_QS).parentNode.parentNode.nextSibling.firstChild.innerText.replace('…', '').replace('...', '');
addSpeech(CHALLENGE_JUDGE_TEXT_QS, hint);
}
function prepareChallengeReadComprehension () {
const speaker1 = document.querySelector(HINT_TOKEN_QS).parentNode.innerText.replaceAll(' ?', '?').replaceAll(' .', '.').replaceAll(' !', '!').replaceAll(' ,', ',');
let speaker2;
if (page.isWrongAnswer) {
speaker2 = document.querySelector(ANSWER_CLASS).innerText;
} else {
speaker2 = document.querySelector(RIGHT_OPTION_QS).innerText;
}
return speaker1 + '\n' + document.querySelector(HINT_TOKEN_QS).parentNode.parentNode.nextSibling.firstChild.innerText.replace('...', '').replace(' ?', '?') + ' ' + speaker2;
}
function introChallengeReadComprehension () {
if (document.querySelector(HINT_TOKEN_QS)) {
const read = document.querySelector(HINT_TOKEN_QS).parentNode.innerText;
const speaker = document.createElement('div');
speaker.innerHTML = speakerButton;
speaker.children[0].id = SPEAK_INTRO;
speaker.children[0].style = 'width:40px; height:40px; background:transparent; margin-left:-7px; margin-bottom:-10px; margin-right:0px;';
document.querySelector(HINT_TOKEN_QS).parentNode.parentNode.insertAdjacentElement('beforeBegin', speaker);
return read;
}
}
}
function setupSpeak () {
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeSpeak(), config.he_cs_auto);
function prepareChallengeSpeak () {
if (document.querySelector(HINT_TOKEN_QS)) {
return document.querySelector(HINT_TOKEN_QS).parentNode.innerText;
}
}
}
function setupTapCloze () {
if (!page.hasIntroSpeakerButton) handleIntroReading(introChallengeTapCloze(), config.he_ctcl_autointro);
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeTapCloze(), config.he_ctcl_auto);
function introChallengeTapCloze () {
const speaker = document.createElement('div');
speaker.innerHTML = speakerButton;
speaker.children[0].id = SPEAK_INTRO;
speaker.children[0].style = 'width:40px; height:40px; background:transparent; padding-bottom:15px; margin-bottom:-10px; margin-right:0px;';
document.querySelectorAll(HINT_TOKEN_QS)[0].insertAdjacentElement('beforeBegin', speaker);
const hintTokens = document.querySelectorAll(HINT_TOKEN_QS);
let intro = '';
for (const token of hintTokens) {
intro += token.innerText;
}
return intro;
}
function prepareChallengeTapCloze () {
if (page.isRightAnswer) {
return document.querySelector(HINT_TOKEN_QS).parentNode.parentNode.innerText.replaceAll('\n', '');
}
if (page.isWrongAnswer) {
const answer = document.querySelector(ANSWER_CLASS);
if (answer.lastElementChild) {
return answer.lastElementChild.innerText;
} else {
return answer.innerText;
}
}
}
}
function setupTapClozeTable () {
if (!page.hasIntroSpeakerButton) handleIntroReading(introChallengeTapClozeTable(), config.he_ctct_autointro);
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeTapClozeTable(), config.he_ctct_auto);
function introChallengeTapClozeTable () {
const speaker = document.createElement('div');
speaker.innerHTML = speakerButton;
speaker.children[0].id = SPEAK_INTRO;
speaker.children[0].style = 'width:40px; height:40px; background:transparent; padding-bottom:15px; margin-bottom:-10px; margin-right:0px;';
document.querySelectorAll(HINT_TOKEN_QS)[1].insertAdjacentElement('beforeBegin', speaker);
return document.querySelectorAll(HINT_TOKEN_QS)[1].innerText;
}
function prepareChallengeTapClozeTable () {
if (page.isRightAnswer) {
// it reads kind of 'vous\t\ns\navez' and should be 'vous savez'
let read = document.querySelectorAll(HINT_TOKEN_QS)[2].parentNode.parentNode.parentNode.innerText.replace('\t\n', ' ').replace('\n', '');
read += '\n' + document.querySelectorAll(HINT_TOKEN_QS)[3].parentNode.parentNode.parentNode.innerText.replace('\t\n', ' ').replace('\n', '');
return read;
}
if (page.isWrongAnswer) {
const answer = document.querySelector(ANSWER_CLASS);
if (answer.lastElementChild) {
return answer.lastElementChild.innerText;
} else {
return answer.innerText;
}
}
}
}
function setupTapComplete () {
if (!page.hasIntroSpeakerButton) handleIntroReading(introChallengeGapFill(), config.he_ctc_autointro);
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeTapComplete(), config.he_ctc_auto);
if (page.isOptionSpeechAdded === false && config.he_ctc_click && document.querySelectorAll(CHALLENGE_TAP_TOKEN_QS).length !== 0) {
addSpeech(CHALLENGE_TAP_TOKEN_QS);
}
function prepareChallengeTapComplete () {
let read;
if (page.isRightAnswer) {
read = document.querySelector(HINT_TOKEN_QS).parentNode.parentNode.innerText.replace(/\n/g, '');
}
if (page.isWrongAnswer) {
read = document.querySelector(ANSWER_CLASS).innerText;
}
return read;
}
}
function setupTypeCloze () {
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeTypeCloze(), config.he_ctyc_auto);
function prepareChallengeTypeCloze () {
let read;
if (page.isRightAnswer) {
const htParent = document.querySelector(HINT_TOKEN_QS).parentNode.parentNode;
const input = htParent.querySelector('input');
input.parentNode.innerText = input.value;
read = htParent.innerText.replaceAll('\n', '');
}
if (page.isWrongAnswer || page.isRightAnswerTypo) {
const answer = document.querySelector(ANSWER_CLASS);
if (answer.lastElementChild) {
read = answer.lastElementChild.innerText;
} else {
read = answer.innerText;
}
}
return read;
}
}
function setupTranslate () {
let configValue = config.he_ct_auto;
if (page.challenge === LISTEN) configValue = config.he_cl_auto;
// complete reverse translation uses the same config as translation, because it looks the same for the user
// if (page.challenge === COMPLETE_REVERSE_TRANSLATION) configValue = config.he_ct_auto;
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareChallengeTranslate(), configValue);
if (!page.isOptionSpeechAdded && config.he_ct_click && !page.hasSpeakerButton && document.querySelectorAll(CHALLENGE_TAP_TOKEN_QS).length !== 0) {
addSpeech(CHALLENGE_TAP_TOKEN_QS, '', true);
}
function prepareChallengeTranslate () {
let read;
if (page.isRightAnswer) {
if (document.querySelector(WORD_BANK_QS)) {
read = document.querySelector(CHALLENGE_TAP_TOKEN_QS).parentNode.parentNode.innerText.replace(/\n/g, ' ');
read = read.replace(/' /g, "'");
} else {
const tI = document.querySelector(TRANSLATE_INPUT_QS);
if (tI.lang === config.lang) read = tI.innerHTML;
}
}
if (page.isWrongAnswer || page.isRightAnswerTypo) {
const answer = document.querySelector(ANSWER_CLASS);
if (answer.lastElementChild) {
read = answer.lastElementChild.innerText;
} else {
read = answer.innerText;
}
}
if (page.hasSpeakerButton) {
read = document.querySelector(HINT_TOKEN_QS).parentNode.innerText;
}
return read;
}
}
function hintTokenSpan (text) {
return `
${text}
\n`;
}
function introChallengeGapFill () {
if (document.querySelector(HINT_TOKEN_QS)) {
const read = document.querySelector(HINT_TOKEN_QS).parentNode.parentNode.innerText;
const speaker = document.createElement('div');
speaker.innerHTML = speakerButton;
speaker.children[0].id = SPEAK_INTRO;
speaker.children[0].style = 'width:40px; height:40px; background:transparent; margin-left:-16px; margin-right:0px; padding-bottom:5px';
document.querySelector(HINT_TOKEN_QS).parentNode.parentNode.insertAdjacentElement('afterBegin', speaker);
return read;
}
}
function setupTip () {
if (!page.hasIntroSpeakerButton && config.he_t_autointro) handleIntroReading(introTip(), config.he_t_autointro);
if (page.isAnswerVisible) renderAnswerSpeakButton(prepareTip(), config.he_t_auto);
if (page.isOptionSpeechAdded === false && config.he_t_click && document.querySelectorAll(CHALLENGE_JUDGE_TEXT).length !== 0) {
addSpeech(CHALLENGE_JUDGE_TEXT);
}
function prepareTip () {
const readArr = document.querySelectorAll(TIP_TEXT_QS);
readArr[readArr.length - 3].replace('/( )+/', document.querySelector(RIGHT_OPTION_QS));
let read = '';
readArr.forEach(function (element, key) {
if (key !== (0 || 1 || 2)) {
read += element.innerText + '\n';
if (readArr[readArr.length - 3].contains(document.querySelector(RIGHT_OPTION_QS))) return read;
}
});
return read;
}
function introTip () {
const readArr = document.querySelectorAll(TIP_TEXT_QS + ' ._1LQ5F ' + HINT_TOKEN_QS);
let read = '';
for (const element of readArr) {
read += (element.innerText).replace(' ', '') + '\n';
}
const speaker = document.createElement('div');
speaker.innerHTML = speakerButton;
speaker.children[0].id = SPEAK_INTRO;
speaker.children[0].style = 'width:40px; height:40px; background:transparent; margin-left:-16px; margin-right:0px; padding-bottom:5px';
document.querySelector(HINT_TOKEN_QS).insertAdjacentElement('beforeBegin', speaker);
return read;
}
}
function handleIntroReading (read = '', click = false) {
if (DEBUG) document.querySelector('#mySentence').innerText = read;
debug('intro = ' + read);
const utter = generateUtter(read);
const speakIntro = document.querySelector(SPEAK_INTRO_QS);
addSpeakListener(SPEAK_INTRO, utter, read);
if (click && speakIntro !== null) {
debug('click intro speaker button');
speakIntro.click();
}
}
function renderAnswerSpeakButton (read = '', auto = false) {
debug('renderAnswerSpeakButton read = ' + read);
const utter = generateUtter(read);
// add speaker button to answer and fill in the correct answer in the headline
updateText(read);
// if we have added the speaker button, we find it in the document
addSpeakListener('speak', utter, read);
// if you like autoplay, it waits 1 second an plays it
if (auto) timeoutAutoplay(utter);
}
function timeoutAutoplay (utter) {
timeout = setTimeout(function () {
debug('auto play ' + page.challenge);
synth.cancel();
synth.speak(utter);
}, config.ap_timeout);
}
function addSpeakListener (id, utter, read) {
const speak = document.querySelector('#' + id);
if (speak) {
speak.addEventListener('click', function () { synth.cancel(); synth.speak(utter); });
if (DEBUG) document.querySelector('#mySentence').innerText = read;
document.getElementById(id).title = read;
} else {
debug('No speak button found');
}
}
function addSpeech (qs, t = '', overrideDuo = false) {
if (t !== '') t += ' ';
const options = document.querySelectorAll(qs);
debug('add speech to options');
options.forEach(function (option, index) {
if ((page.challenge !== MATCH) || (page.challenge === MATCH && index > 4)) {
const utter = generateUtter(t + option.innerText);
option.parentNode.addEventListener('click', function () {
if (overrideDuo) page.isReadingOptions = true;
debug('Option read = ' + t + option.innerText);
synth.cancel();
synth.speak(utter);
});
}
});
page.isOptionSpeechAdded = true;
}
function generateUtter (read) {
const utter = new SpeechSynthesisUtterance(read);
utter.voice = voices[voiceSelect];
utter.volume = 1;
utter.pitch = 1;
utter.rate = 1;
utter.lang = config.lang;
return utter;
}
function myShortcutListener (event) {
const speak = document.querySelector('#speak');
const duoSpeak = document.querySelector(SPEAKER_BUTTON_QS);
// ALT + l combo
if (event.altKey && event.key === 'l') {
if (speak) speak.click();
else if (duoSpeak) duoSpeak.click();
debug('alt = ' + event.altKey + ' + ' + event.key);
}
}
// gives some debug information directly in the Duo-GUI
function buildDebug () {
if (DEBUG && !document.querySelector('#myChallenge')) {
let autoPlay = 'disabled';
let speakOptions = 'disabled';
let autoIntro = 'disabled';
autoPlay = getEnabled(autoPlay, TRANSLATE, config.he_ct_auto);
autoPlay = getEnabled(autoPlay, GAP_FILL, config.he_cgf_auto);
speakOptions = getEnabled(speakOptions, GAP_FILL, config.he_cgf_click);
autoPlay = getEnabled(autoPlay, TAP_COMPLETE, config.he_ctc_auto);
speakOptions = getEnabled(speakOptions, TAP_COMPLETE, config.he_ctc_click);
autoPlay = getEnabled(autoPlay, FORM, config.he_cf_auto);
speakOptions = getEnabled(speakOptions, FORM, config.he_cf_click);
autoPlay = getEnabled(autoPlay, DIALOGUE, config.he_cd_auto);
speakOptions = getEnabled(speakOptions, DIALOGUE, config.he_cd_click);
autoIntro = getEnabled(autoIntro, DIALOGUE, config.he_cd_autointro);
autoPlay = getEnabled(autoPlay, NAME, config.he_cn_auto);
speakOptions = getEnabled(speakOptions, LISTEN_COMPREHENSION, config.he_clc_click);
autoPlay = getEnabled(autoPlay, SPEAK, config.he_cs_auto);
buildDebugDiv(speakOptions, autoPlay, autoIntro);
}
// sets a option to 'enabled' if challengeName and configOption are true for this page
function getEnabled (option, challengeName, configOption) {
if (page.challenge === challengeName && configOption) option = 'enabled';
return option;
}
function buildDebugDiv (speakOptions, autoPlay, autoIntro) {
const debugDiv = document.createElement('div');
debugDiv.innerHTML = `
Challenge-Name: ${getChallengeType()[0]}
Sentence to speak:
Speak options: ${speakOptions}
Auto play: ${autoPlay}
Auto intro: ${autoIntro}
Not found: `;
debugDiv.style = 'font-size: small; text-align:left; display:grid;';
document.querySelector('[data-test="challenge-header"]').insertAdjacentElement('afterend', debug);
if (!document.querySelector(HINT_SENTENCE_QS)) {
document.querySelector('#myNotFound').innerText += ' HINT_SENTENCE: ' + HINT_SENTENCE;
}
if (!document.querySelector(SPEAKER_BUTTON_QS)) {
document.querySelector('#myNotFound').innerText += ' SPEAKER_BUTTON: ' + SPEAKER_BUTTON;
}
}
}
function checkNewPage () {
if (!document.querySelector('#myNewPage')) {
page.challenge = getChallengeType()[0];
// if (!page.challenge) { page.challenge = 'tip'; }
const nP = document.createElement('div');
nP.id = 'myNewPage';
document.querySelector('[data-test="challenge-header"]').insertAdjacentElement('beforeend', nP);
debug('Challenge Type = ' + page.challenge);
page.isNewPage = true;
if (howlPlay === false) {
synth.cancel();
} else {
howlPlay = false;
}
}
}
// TODO: remove returned array
// returns challenge type or false
function getChallengeType () {
const element = document.querySelector('[data-test~="challenge"]');
if (element !== null) {
return [element.getAttribute('data-test').split(' ')[1], element];
} else {
return [false];
}
}
function updateText (t) {
// don't add a listen button if there is no text t
if (t !== '') {
const translateInput = document.querySelector(TRANSLATE_INPUT_QS);
const div = document.createElement('div');
div.class = 'np6Tv';
div.style = 'position: absolute; align-self: flex-end; top: 1.8rem;';
div.innerHTML = speakerButton;
// if the answer is displayed
if (document.querySelector(ANSWER_QS)) {
if (translateInput !== null) {
if (translateInput.lang === config.lang) {
document.querySelector(ANSWER_QS).parentNode.insertAdjacentElement('afterBegin', div);
}
} else {
document.querySelector(ANSWER_QS).parentNode.insertAdjacentElement('afterBegin', div);
}
}
}
}