Greasy Fork

Greasy Fork is available in English.

Duohacker

An autoanswer script for Duolingo.

当前为 2024-04-01 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name             Duohacker
// @namespace        https://www.duolingo.com/
// @homepageURL      https://github.com/elvisoliveira/duohacker
// @supportURL       https://github.com/elvisoliveira/duohacker/issues
// @version          1.0.7
// @description      An autoanswer script for Duolingo.
// @author           elvisoliveira
// @match            https://www.duolingo.com/practice*
// @match            https://www.duolingo.com/learn*
// @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)();