Greasy Fork

Duohacker

An autoanswer script for Duolingo.

目前为 2024-04-01 提交的版本。查看 最新版本

// ==UserScript==
// @name             Duohacker
// @namespace        https://www.duolingo.com/
// @homepageURL      https://github.com/elvisoliveira/duohacker
// @supportURL       https://github.com/elvisoliveira/duohacker/issues
// @version          1.0.6
// @description      An autoanswer script for Duolingo.
// @author           elvisoliveira
// @match            https://www.duolingo.com/practice*
// @license          MIT
// @grant            none
// @run-at           document-end
// ==/UserScript==

const DEBUG = false;
let mainInterval;
const dataTestComponentClassName = ".e4VJZ, .FQpeZ, ._35e5D";
const TIME_OUT = 3000;

// Challenge types
const CHARACTER_SELECT_TYPE = "characterSelect";
const CHARACTER_MATCH_TYPE = "characterMatch";
const TRANSLATE_TYPE = "translate";
const LISTEN_TAP_TYPE = "listenTap";
const NAME_TYPE = "name";
const COMPLETE_REVERSE_TRANSLATION_TYPE = "completeReverseTranslation";
const LISTEN_TYPE = "listen";
const SELECT_TYPE = "select";
const JUDGE_TYPE = "judge";
const FORM_TYPE = "form";
const LISTEN_COMPREHENSION_TYPE = "listenComprehension";
const READ_COMPREHENSION_TYPE = "readComprehension";
const CHARACTER_INTRO_TYPE = "characterIntro";
const DIALOGUE_TYPE = "dialogue";
const SELECT_TRANSCRIPTION_TYPE = "selectTranscription";
const SPEAK_TYPE = "speak";
const SELECT_PRONUNCIATION_TYPE = "selectPronunciation";
const LISTEN_ISOLATION_TYPE = "listenIsolation";
const TAP_COMPLETE_TABLE_TYPE = "tapCompleteTable";
const TYPE_COMPLETE_TABLE_TYPE = "typeCompleteTable";
const TYPE_CLOSE_TYPE = "typeCloze";
const TYPE_CLOSE_TABLE_TYPE = "typeClozeTable";
const TAP_CLOSE_TABLE_TYPE = "tapClozeTable";
const TAP_CLOSE_TYPE = "tapCloze";
const ASSIST_TYPE = "assist";
const LISTEN_MATCH_TYPE = "listenMatch";
const LISTEN_COMPLETE_TYPE = "listenComplete";
const LISTEN_SPELL_TYPE = "listenSpell";
const TAP_COMPLETE_TYPE = "tapComplete";
const MATCH_TYPE = "match";
const GAP_FILL_TYPE = "gapFill";
const CHARACTER_TRACE_TYPE = "characterTrace";
const CHALLENGE_PUZZLE_TYPE = "characterPuzzle";
const DEFINITION_TYPE = "definition";
const TAP_DESCRIBE_TYPE = "tapDescribe";
const FREE_RESPONSE_TYPE = "freeResponse";
const PARTIAL_REVERSE_TRANSLATE_TYPE = "partialReverseTranslate";

// Query DOM keys
const CHALLENGE_CHOICE_CARD = '[data-test="challenge-choice-card"]';
const CHALLENGE_CHOICE = '[data-test="challenge-choice"]';
const CHALLENGE_TRANSLATE_INPUT = '[data-test="challenge-translate-input"]';
const CHALLENGE_LISTEN_TAP = '[data-test="challenge-listenTap"]';
const CHALLENGE_LISTEN_SPELL = '[data-test="challenge challenge-listenSpell"]';
const CHALLENGE_JUDGE_TEXT = '[data-test="challenge-judge-text"]';
const CHALLENGE_TEXT_INPUT = '[data-test="challenge-text-input"]';
const CHALLENGE_TAP_TOKEN = '[data-test*="challenge-tap-token"]';
const CHALLENGE_TAP_TOKEN_TEXT = '[data-test="challenge-tap-token-text"]';
const CHALLENGE_TYPE_CLOZE = '[data-test="challenge challenge-typeCloze"]';
const CHALLENGE_TYPE_CLOZE_TABLE = '[data-test="challenge challenge-typeClozeTable"]';
const PLAYER_NEXT = '[data-test="player-next"], [data-test="story-start"]';
const PLAYER_SKIP = '[data-test="player-skip"]';
const AUDIO_BUTTON = '[data-test="audio-button"]';
const WORD_BANK = '[data-test="word-bank"]';
const BLAME_INCORRECT = '[data-test="blame blame-incorrect"]';
const CHARACTER_MATCH = '[data-test="challenge challenge-characterMatch"]';
const TYPE_COMPLETE_TABLE = '[data-test="challenge challenge-typeCompleteTable"]';
const STORIES_PLAYER_NEXT = '[data-test="stories-player-continue"]';
const STORIES_PLAYER_START = '[data-test="story-start"]';
const STORIES_PLAYER_DONE = '[data-test="stories-player-done"]';
const STORIES_CHOICE = '[data-test="stories-choice"]';
const STORIES_ELEMENT = '[data-test="stories-element"]';

// Mouse Click
const clickEvent = new MouseEvent("click", {
  view: window,
  bubbles: true,
  cancelable: true,
});

// Gets Challenge Object
function getChallengeObj(theObject) {
  let result = null;
  if (theObject instanceof Array) {
    for (let i = 0; i < theObject.length; i++) {
      result = getChallengeObj(theObject[i]);
      if (result) {
        break;
      }
    }
  } else {
    for (let prop in theObject) {
      if (prop == "challenge") {
        if (typeof theObject[prop] == "object") {
          return theObject;
        }
      }
      if (
        theObject[prop] instanceof Object ||
        theObject[prop] instanceof Array
      ) {
        result = getChallengeObj(theObject[prop]);
        if (result) {
          break;
        }
      }
    }
  }
  return result;
}

function getElementIndex(element) {
  let result = null;
  if (element instanceof Array) {
    for (let i = 0; i < element.length; i++) {
      result = getElementIndex(element[i]);
      if (result) {
        break;
      }
    }
  } else {
    for (let prop in element) {
      // if (prop == "elementIndex") {
      if (prop == "challenge") {
        if (typeof element[prop] == "object") {
          return element;
        }
        return element[prop];
      }
      if (element[prop] instanceof Object || element[prop] instanceof Array) {
        result = getElementIndex(element[prop]);
        if (result) {
          break;
        }
      }
    }
  }
  return result;
}

function getProps(element) {
  let propsClass = Object.keys(element).filter((att) => /^__reactProps/g.test(att))[0];
  return element[propsClass];
}

// Gets the Challenge
function getChallenge() {
  const dataTestDOM = document.querySelectorAll(dataTestComponentClassName);
  if (dataTestDOM.length > 0) {
    let current = 0;
    for (let i = 0; i < dataTestDOM.length; i++) {
      if(dataTestDOM[i].childNodes.length > 0) {
        current = i;
      }
    }
    let currentDOM = dataTestDOM[current];
    let propsValues = getProps(currentDOM);
    const { challenge } = getElementIndex(propsValues);
    // let elementIndex = getElementIndex(propsValues);
    // let storyList = getProps(currentDOM.parentNode);
    // return storyList.children.find(Boolean)[elementIndex].props.storyElement;
    return challenge;
  }
}

// https://stackoverflow.com/a/39165137
// https://github.com/Venryx/mobx-devtools-advanced/blob/master/Docs/TreeTraversal.md
function getReactFiber(dom) {
  const key = Object.keys(dom).find((key) => {
    return (
      key.startsWith("__reactFiber$") || // react 17+
      key.startsWith("__reactInternalInstance$") // react <17
    );
  });
  return dom[key];
}

// pressEnter() function
function pressEnter() {
  const clickEvent = new MouseEvent("click", {
    view: window,
    bubbles: true,
    cancelable: false,
  });
  breakWhenIncorrect();
  let isPlayerNext = document.querySelector(PLAYER_NEXT);
  if (isPlayerNext !== null) {
    isPlayerNext.dispatchEvent(clickEvent);
  }
}

// pressEnter() function but for stories
function pressEnterStories() {
  const clickEvent = new MouseEvent("click", {
    view: window,
    bubbles: true,
    cancelable: false,
  });
  document.querySelector([STORIES_PLAYER_NEXT, STORIES_PLAYER_START, STORIES_PLAYER_DONE].join(',')).dispatchEvent(clickEvent);
}

// dynamicInput() function
function dynamicInput(element, text) {
  let input = element;
  let lastValue = input.value;
  input.value = text;
  let event = new Event("input", { bubbles: true });
  event.simulated = true;
  let tracker = input._valueTracker;
  if (tracker) {
    tracker.setValue(lastValue);
  }
  input.dispatchEvent(event);
}

// Solves the Challenge
function classify() {
  const challenge = getChallenge();
  if (!challenge) return;
  if (DEBUG) terminal.log(`${challenge.type}`, challenge);
  switch (challenge.type) {
    case 'MULTIPLE_CHOICE':
    case 'SELECT_PHRASE': {
      const { correctAnswerIndex } = challenge;
      if (DEBUG) { terminal.log("MULTIPLE_CHOICE_TYPE, SELECT_PHRASE", { correctAnswerIndex }); }
      const options = document.querySelectorAll('[data-test="stories-choice"]');
      options.forEach((option, i) => {
        if(i == correctAnswerIndex) {
          option.dispatchEvent(clickEvent);
        }
      });
      return { correctAnswerIndex };
    }

    case 'POINT_TO_PHRASE': {
      const { correctAnswerIndex } = challenge;
      if (DEBUG) { terminal.log("POINT_TO_PHRASE", { correctAnswerIndex }); }
      const options = document.querySelectorAll('[data-test="challenge-tap-token"]');
      options.forEach((option, i) => {
        if(i == correctAnswerIndex) {
          option.dispatchEvent(clickEvent);
        }
      });
      return { correctAnswerIndex };
    }

    case 'ARRANGE': {
      const { selectablePhrases, phraseOrder } = challenge;
      if (DEBUG) { terminal.log("POINT_TO_PHRASE", { selectablePhrases, phraseOrder }); }
      const options = document.querySelectorAll('[data-test="challenge-tap-token"]');
      if(options.length > 0) {
        phraseOrder.forEach((i) => {
            Array.prototype.slice.call(options).filter(function(option) { return option.innerText == selectablePhrases[i] }).find(Boolean).dispatchEvent(clickEvent);
        });
      }
      return { selectablePhrases, phraseOrder };
    }

    case 'MATCH': {
      const { matches } = challenge;
      if (DEBUG) { terminal.log("MATCH", { matches }); }
      const options = document.querySelectorAll('[data-test="challenge-tap-token"]');
      matches.forEach((match) => {
        options.forEach((option) => {
          if(option.querySelector('[data-test="challenge-tap-token-text"]').innerText == match.phrase || option.querySelector('[data-test="challenge-tap-token-text"]').innerText == match.translation) {
            option.dispatchEvent(clickEvent);
          }
        });
      });
      return { matches };
    }

    case LISTEN_SPELL_TYPE: {
      const { displayTokens } = challenge;
      if (DEBUG) { terminal.log("LISTEN_SPELL_TYPE", { displayTokens }); }
      const tokens = document.querySelectorAll(CHALLENGE_LISTEN_SPELL.concat(' input[type="text"]:not([readonly])'));
      let i = 0;
      displayTokens.forEach((word) => {
        if(!isNaN(word.damageStart)) {
          for (let c of word.text.substring(word.damageStart, word.damageEnd ?? word.text.length)) {
            dynamicInput(tokens[i], c);
            i++;
          }
        }
      });
      return { displayTokens };
    }

    case LISTEN_COMPLETE_TYPE: {
      const { displayTokens } = challenge;
      if (DEBUG) { terminal.log("LISTEN_COMPLETE_TYPE", { displayTokens }); }
      let tokens = document.querySelectorAll(CHALLENGE_TEXT_INPUT);
      var i = 0;
      displayTokens.forEach((token) => {
        if(token.isBlank) {
            dynamicInput(tokens[i], token.text);
        }
      });
      return { displayTokens };
    }

    case PARTIAL_REVERSE_TRANSLATE_TYPE: {
      const { displayTokens, grader } = challenge;
      if (DEBUG) { terminal.log("PARTIAL_REVERSE_TRANSLATE_TYPE", { displayTokens, grader }); }
      let tokens = document.querySelectorAll("[contenteditable=true]");
      let value = '';
      let event = new Event("input", { bubbles: true });
      event.simulated = true;
      displayTokens.forEach((token) => {
        if(token.isBlank) {
            value = value + token.text;
        }
      });
      try {
        tokens[0].textContent = value;
        tokens[0].dispatchEvent(event);
      } catch (e) { /* terminal.log(e); */ }
      return { displayTokens, grader };
    }

    case LISTEN_MATCH_TYPE: {
      const { pairs } = challenge;
      if (DEBUG) { terminal.log("LISTEN_MATCH_TYPE", { pairs }); }
      const tokens = document.querySelectorAll('button'.concat(CHALLENGE_TAP_TOKEN));
      for (let i = 0; i <= 3; i++) {
        const dataset = getReactFiber(tokens[i]).return.child.stateNode.dataset.test;
        const word = dataset.split('-')[0];
        tokens[i].dispatchEvent(clickEvent);
        for (let j = 4; j <= 7; j++) {
          const text = tokens[j].querySelector(CHALLENGE_TAP_TOKEN_TEXT).innerText;
          if(/\s/g.test(dataset) && text.endsWith(` ${word}`)) {
            tokens[j].dispatchEvent(clickEvent);
          } else if (text == word) {
            tokens[j].dispatchEvent(clickEvent);
          }
        }
      }
      return { pairs }
    }

    case DEFINITION_TYPE:
    case ASSIST_TYPE: {
      const { choices, correctIndex } = challenge;
      const tokens = document.querySelectorAll(CHALLENGE_CHOICE);
      if (DEBUG) { terminal.log("ASSIST_TYPE, DEFINITION_TYPE", { choices, correctIndex, tokens }); }
      tokens.forEach((e, i) => {
        if(i == correctIndex) {
          e.dispatchEvent(clickEvent);
        }
      });
      return { choices, correctIndex };
    }

    case GAP_FILL_TYPE:
    case SELECT_TYPE:
    case SELECT_PRONUNCIATION_TYPE:
    case READ_COMPREHENSION_TYPE:
    case LISTEN_COMPREHENSION_TYPE:
    case CHARACTER_SELECT_TYPE:
    case FORM_TYPE: {
      const { choices, correctIndex } = challenge;
      if (DEBUG) {
        terminal.log(
          "READ_COMPREHENSION, LISTEN_COMPREHENSION, CHARACTER_SELECT_TYPE, GAP_FILL_TYPE, SELECT_PRONUNCIATION_TYPE",
          { choices, correctIndex }
        );}
      document
        .querySelectorAll(CHALLENGE_CHOICE)
        [correctIndex].dispatchEvent(clickEvent);
      return { choices, correctIndex };
    }

    case TAP_COMPLETE_TABLE_TYPE: {
      const { choices, displayTokens } = challenge;
      const tokens = document.querySelectorAll(WORD_BANK.concat(' ', CHALLENGE_TAP_TOKEN));
      if (DEBUG) { terminal.log("TAP_COMPLETE_TABLE_TYPE", { choices, displayTokens, tokens }); }
      displayTokens.forEach((line) => {
        line.forEach((column) => {
          if(column[0].isBlank == true) {
            tokens.forEach((e) => {
              if(e.innerText == column[0].text) {
                e.dispatchEvent(clickEvent);
              }
            });
          }
        });
      });
      return { choices, displayTokens };
    }

    case TYPE_COMPLETE_TABLE_TYPE: {
      const { displayTokens } = challenge;
      const tokens = document.querySelectorAll(TYPE_COMPLETE_TABLE.concat(' input'));
      if (DEBUG) { terminal.log("TYPE_COMPLETE_TABLE_TYPE", { displayTokens, tokens }); }
      var index = 0;
      displayTokens.forEach((line) => {
        line.forEach((column) => {
          if(column[0].isBlank == true) {
            dynamicInput(tokens[index], column[0].text);
            index++;
          }
        });
      });
      return { displayTokens };
    }

    case TYPE_CLOSE_TYPE: {
      const { displayTokens } = challenge;
      const tokens = document.querySelectorAll(CHALLENGE_TYPE_CLOZE.concat(' input'));
      if (DEBUG) { terminal.log("TYPE_CLOSE_TYPE", { displayTokens, tokens }); }
      let i = 0;
      displayTokens.forEach((word) => {
        if(word.damageStart) {
          dynamicInput(tokens[i], word.text.substring(word.damageStart, word.text.length));
          i++;
        }
      });
      return { displayTokens };
    }

    case TYPE_CLOSE_TABLE_TYPE: {
      const { displayTokens } = challenge;
      const tokens = document.querySelectorAll(CHALLENGE_TYPE_CLOZE_TABLE.concat(' input'));
      if (DEBUG) { terminal.log("TYPE_CLOSE_TABLE_TYPE", { displayTokens, tokens }); }
      let i = 0;
      displayTokens.forEach((line) => {
        line.forEach((column) => {
          column.forEach((word) => {
            if(word.damageStart) {
              dynamicInput(tokens[i], word.text.substring(word.damageStart, word.text.length));
              i++;
            }
          });
        });
      });
      return { displayTokens };
    }

    case TAP_CLOSE_TABLE_TYPE: {
      const { displayTokens } = challenge;
      const tokens = document.querySelectorAll(CHALLENGE_TAP_TOKEN_TEXT);
      if (DEBUG) { terminal.log("TYPE_CLOSE_TABLE_TYPE", { displayTokens, tokens }); }
      displayTokens.forEach((line) => {
        line.forEach((column) => {
          column.forEach((word) => {
            if(word.damageStart) {
              tokens.forEach((token) => {
                if(token.innerText == word.text.substring(word.damageStart, word.text.length)) {
                  token.dispatchEvent(clickEvent);
                }
              });
            }
          });
        });
      });
      return { displayTokens };
    }

    case TAP_CLOSE_TYPE: {
      const { choices, correctIndices } = challenge;
      const tokens = document.querySelectorAll(CHALLENGE_TAP_TOKEN);
      if (DEBUG) { terminal.log("TAP_CLOSE_TYPE", { choices, correctIndices, tokens }); }
      for (let i = 0; i < correctIndices.length; i++) {
        choices.forEach((value, j) => {
          if(correctIndices[i] == j) {
            for (let k = 0; k < tokens.length; k++) {
              if(tokens[k].innerText == value) {
                tokens[k].dispatchEvent(clickEvent);
              }
            }
          }
        });
      };
      return { choices, correctIndices };
    }

    case TAP_COMPLETE_TYPE: {
      const { choices, correctIndices } = challenge;
      const tokens = document.querySelectorAll(WORD_BANK.concat(' ', CHALLENGE_TAP_TOKEN_TEXT));
      if (DEBUG) {terminal.log("TAP_COMPLETE_TYPE", { choices, correctIndices, tokens });}
      correctIndices.forEach((i) => {
        tokens[i].dispatchEvent(clickEvent);
      });
      return { choices, correctIndices };
    }

    case LISTEN_ISOLATION_TYPE: {
      const { correctIndex } = challenge;
      const tokens = document.querySelectorAll(CHALLENGE_CHOICE);
      if (DEBUG) {terminal.log("LISTEN_ISOLATION_TYPE", { correctIndex, tokens });}
      tokens.forEach((e, i) => {
        if(i == correctIndex) {
          e.dispatchEvent(clickEvent);
        }
      });
      return { correctIndex };
    }

    case MATCH_TYPE: {
      const { pairs } = challenge;
      const tokens = document.querySelectorAll(CHALLENGE_TAP_TOKEN_TEXT);
      if (DEBUG) {terminal.log("MATCH_TYPE", { tokens, pairs });}
      pairs.forEach((pair) => {
        for (let i = 0; i < tokens.length; i++) {
          if (
            tokens[i].innerText === pair.fromToken ||
            tokens[i].innerText === pair.learningToken
          ) {
            tokens[i].dispatchEvent(clickEvent);
          }
        }
      });
      return { pairs };
    }

    case CHARACTER_MATCH_TYPE: {
      const { pairs } = challenge;
      const tokens = document.querySelectorAll(CHALLENGE_TAP_TOKEN);
      if (DEBUG) {terminal.log("CHARACTER_MATCH_TYPE", { tokens, pairs });}
      pairs.forEach((pair) => {
        for (let i = 0; i < tokens.length; i++) {
          if (
            tokens[i].innerText === pair.fromToken ||
            tokens[i].innerText === pair.learningToken
          ) {
            tokens[i].dispatchEvent(clickEvent);
          }
        }
      });
      return { pairs };
    }

    case TRANSLATE_TYPE: {
      const { correctTokens, correctSolutions } = challenge;
      if (DEBUG) {terminal.log("TRANSLATE_TYPE", { correctTokens, correctSolutions });}
      if (correctTokens) {
        const tokens = document.querySelectorAll(CHALLENGE_TAP_TOKEN_TEXT);
        let ignoreTokeIndexes = [];
        for (let correctTokenIndex in correctTokens) {
          for (let tokenIndex in tokens) {
            const token = tokens[tokenIndex];
            if (ignoreTokeIndexes.includes(tokenIndex)) continue;
            if (token.innerText === correctTokens[correctTokenIndex]) {
              token.dispatchEvent(clickEvent);
              ignoreTokeIndexes.push(tokenIndex);
              if (DEBUG)
                terminal.log(
                  `correctTokenIndex [${correctTokens[correctTokenIndex]}] - tokenIndex [${token.innerText}]`
                );
              break;
            }
          }
        }
      } else if (correctSolutions) {
        let textInputElement = document.querySelectorAll(
          CHALLENGE_TRANSLATE_INPUT
        )[0];
        dynamicInput(textInputElement, correctSolutions[0]);
      }
      return { correctTokens };
    }

    case NAME_TYPE: {
      const { correctSolutions, articles, grader } = challenge;
      if (DEBUG) { terminal.log("NAME_TYPE", { correctSolutions, articles, grader }); }
      let tokens = document.querySelectorAll(CHALLENGE_TEXT_INPUT);
      if(articles) {
        correctSolutions.forEach((solution) => {
          solution = solution.split(' ');
          solution.forEach((word) => {
            let i = articles.indexOf(word);
            if(i > -1) {
              document.querySelectorAll(CHALLENGE_CHOICE)[i].dispatchEvent(clickEvent);
              solution.splice(solution.indexOf(word), 1);
              dynamicInput(tokens[0], solution.join(' '));
            }
          });
        });
      }
      else {
        correctSolutions.forEach((solution, i) => {
          dynamicInput(tokens[0], solution);
        });
      }
      return { correctSolutions, articles, grader };
    }

    case COMPLETE_REVERSE_TRANSLATION_TYPE: {
      const { displayTokens } = challenge;
      if (DEBUG) {terminal.log("COMPLETE_REVERSE_TRANSLATION_TYPE", { displayTokens });}
      let tokens = document.querySelectorAll(CHALLENGE_TEXT_INPUT);
      var i = 0;
      displayTokens.forEach((token) => {
        if(token.isBlank) {
            dynamicInput(tokens[i], token.text);
            i++;
        }
      });
      return { displayTokens };
    }

    case LISTEN_TAP_TYPE: {
      const { correctTokens } = challenge;
      if (DEBUG) {terminal.log("LISTEN_TAP_TYPE", { correctTokens });}
      const tokens = Array.from(document.querySelectorAll(CHALLENGE_TAP_TOKEN)).filter(e => e.tagName === 'BUTTON');
      for (let word of correctTokens) {
        for (let i of Object.keys(tokens)) {
          if (tokens[i].innerText === word) {
            tokens[i].dispatchEvent(clickEvent);
            tokens.splice(i, 1);
            break;
          }
        }
      }
      return { correctTokens };
    }

    case LISTEN_TYPE: {
      const { prompt } = challenge;
      if (DEBUG) {terminal.log("LISTEN_TYPE", { prompt });}
      let textInputElement = document.querySelectorAll(
        CHALLENGE_TRANSLATE_INPUT
      )[0];
      dynamicInput(textInputElement, prompt);
      return { prompt };
    }

    case JUDGE_TYPE: {
      const { correctIndices } = challenge;
      if (DEBUG) {terminal.log("JUDGE_TYPE", { correctIndices });}
      document
        .querySelectorAll(CHALLENGE_JUDGE_TEXT)
        [correctIndices[0]].dispatchEvent(clickEvent);
      return { correctIndices };
    }

    case DIALOGUE_TYPE:
    case CHARACTER_INTRO_TYPE: {
      const { choices, correctIndex } = challenge;
      if (DEBUG)
        {terminal.log("CHARACTER_INTRO_TYPE, DIALOGUE_TYPE", {
          choices,
          correctIndex,
        });}
      document
        .querySelectorAll(CHALLENGE_JUDGE_TEXT)
        [correctIndex].dispatchEvent(clickEvent);
      return { choices, correctIndex };
    }

    case SELECT_TRANSCRIPTION_TYPE: {
      const { choices, correctIndex } = challenge;
      if (DEBUG) {terminal.log("SELECT_TRANSCRIPTION_TYPE", { choices, correctIndex });}
      document
        .querySelectorAll(CHALLENGE_JUDGE_TEXT)
        [correctIndex].dispatchEvent(clickEvent);
      return { choices, correctIndex };
    }

    case SPEAK_TYPE: {
      const { prompt } = challenge;
      if (DEBUG) {terminal.log("SPEAK_TYPE", { prompt });}
      document.querySelectorAll(PLAYER_SKIP)[0].dispatchEvent(clickEvent);
      return { prompt };
    }

    default:
      break;
  }
}

// Stops when an answer is incorrect
function breakWhenIncorrect() {
  const isBreak = document.querySelectorAll(BLAME_INCORRECT).length > 0;
  if (isBreak) {
    terminal.log("Incorrect, stopped");
    clearInterval(mainInterval);
  }
}

// Main Function
function main() {
  try {
    let isPlayerNext = document.querySelectorAll(PLAYER_NEXT);
    let test = document.querySelector("[data-test=\"plus-no-thanks\"], [data-test=\"practice-hub-ad-no-thanks-button\"]");
    if(isPlayerNext !== null && isPlayerNext.length > 0) {
      if(isPlayerNext[0].getAttribute('aria-disabled') === 'true')
        classify();
    } else if (test !== null && test.length > 0) {
      test.click();
    } else if (/learn/gi.test(window.location.href) == true) {
      learn();
    }
    setTimeout(pressEnter, 150); // pressEnter();
  } catch (e) {
    // terminal.log(e);
  }
}

// Stories Function
function stories() {
  if (/stories/gi.test(window.location.href) == false) {
    window.location.replace("https://www.duolingo.com/stories"); return;
  }
  try {
    let button = document.querySelectorAll([STORIES_PLAYER_NEXT, STORIES_PLAYER_START, STORIES_PLAYER_DONE].join(','));
    if(button.length > 0) {
      let action = Array.prototype.slice.call(button).find(Boolean).textContent.toUpperCase();
      if (action.valueOf() == "START STORY") {
        setTimeout(pressEnterStories, 150);
      } else if (action.valueOf() == "CONTINUE") {
        if(Array.prototype.slice.call(button).find(Boolean).disabled) {
          classify();
        } else {
          setTimeout(pressEnterStories, 150);
        }
      }
    } else {
      let button = document.querySelector('[data-test="story-start-button"]');
      if(button) {
        if(button.nextSibling.disabled) {
          button.dispatchEvent(clickEvent);
        } else {
          button.nextSibling.dispatchEvent(clickEvent);
        }
      } else {
        document.querySelector('div._2zY7s:not([style*="255, 177, 0"]):not([style*="251, 177, 0"])').dispatchEvent(clickEvent);
      }
    }
  } catch (e) {
    terminal.log(e);
  }
}

// Calls main()
function solveChallenge() {
  if(document.getElementById('mylog') == null) {
    setConsole();
  }
  // Check if its a Skill / Alphabet / Checkpoint URL
  // if (/[sac][klh][ipe][lhc][lak]/gi.test(window.location.href) == true) {
  if (/lesson|practice/gi.test(window.location.href) == true) {
    if (DEBUG) {terminal.log("Skill URL Detected");}
    clearInterval(mainInterval);
    mainInterval = setInterval(main, TIME_OUT);
  }
  // Check if its a Stories URL
  if (/stories/gi.test(window.location.href) == true) {
    if (DEBUG) {terminal.log("Stories URL Detected");}
    clearInterval(mainInterval);
    mainInterval = setInterval(stories, TIME_OUT);
  }
  if (/learn/gi.test(window.location.href) == true) {
    if (DEBUG) {terminal.log("Main URL Detected");}
    window.location.replace("https://www.duolingo.com/practice");
  }
  terminal.log(`to stop the script run "clearInterval(${mainInterval})"`);
}

function learn() {
  let button = document.querySelector('a[data-test="global-practice"]');
  if(button) {
      button.click();
  }
  clearInterval(mainInterval);
  mainInterval = setInterval(solveChallenge, TIME_OUT);
}

// To not mess duolingo own log
function setConsole() {
  var iframe = document.createElement('iframe');
  iframe.id = 'mylog';
  iframe.style.display = 'none';
  document.body.appendChild(iframe);
  window.terminal = iframe.contentWindow.console;
}

(solveChallenge)();