Greasy Fork is available in English.

Duolingo mods

Duolingo keyboard shortcuts

// ==UserScript==
// @name         Duolingo mods
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Duolingo keyboard shortcuts
// @author       You
// @require      https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.18.2/babel.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.16.0/polyfill.js
// @match        https://www.duolingo.com/*
// ==/UserScript==
// constants
const home_class = '_2QyU5'
const strengthen_class = '_1_mnP _3QG2_ _1vaUe _3IS_q'
const practice_class = '_1A_Px oNqWF _3hso2 _2vmUZ _2Zh2S _1X3l0 eJd0I _3yrdh _2wXoR _1AM95 _2PUh7 H7AnT'
const discuss_class = 'uRLFE _11g-P _2yH8S _3j92s'
const untimed_class = '_3_pD1 _2ESN4 _2arQ0 _2vmUZ _2Zh2S _1X3l0 eJd0I _3yrdh _2wXoR _1AM95 _1dlWz _2gnHr _2L5kw _3Ry1f'
const test_out_class = 'cVLwd _2arQ0 _2vmUZ _2Zh2S _1X3l0 eJd0I _3yrdh _2wXoR _1AM95 _1dlWz _2gnHr _3Ry1f _2VaJD _23-CV _2arQ0 _2vmUZ _2Zh2S _1X3l0 eJd0I _3yrdh _2wXoR _1AM95 _1dlWz _2gnHr'
const start_lesson_class = 'TZE8O'
const hint_class = '_1FxPb _3RFcO _3kmYT _2vmUZ _2Zh2S _1X3l0 eJd0I _3yrdh _2wXoR _1AM95 _3lAD6 H7AnT'
const test_class = '_3Gihx'
const textarea_class = '_2MGCg _1py6s _1e69E _3_NyK _1Juqt'
const replay_audio_class = '_2GN1p _1ZlfW _2cIrv'
const choice_class = 'iNLw3'
const mouseover_character_class = '_2KZ79'
const asdfg_arr = ['a','s','d','f','g']
const qwert_arr = ['q','w','e','r','t']
const hjkl_arr  = ['h','j','k','l',';']
// variables
let last_selected_index = -1
let mouseover_active = false
// helpers
const getEls = class_name => document.getElementsByClassName(class_name)
function clickIndex(class_name, idx) {
  const els = getEls(class_name)
  if (els.length > idx) {
    els[idx].click()
    return true
  }
  return false
}
const clickFirst = class_name => clickIndex(class_name, 0)
function mouseoverCharacter(idx) {
  const mouseover_els = getEls(mouseover_character_class)
  if (idx < 0) {
    idx = mouseover_els.length - 1
  } else if (idx >= mouseover_els.length) {
    idx = 0
  }
  for (let i = 0; i < mouseover_els.length; ++i) {
    const el = mouseover_els[i]
    if (el.className == mouseover_character_class || i == idx) {
      el.click()
    }
  }
  mouseover_active = (idx !== last_selected_index)
  last_selected_index = idx
}
// core functionality
function onKey(e) {
  console.warn(e)
  const key = e.key.toLowerCase()
  if (key == '`') {
    clickFirst(textarea_class)
  } else if (key == ' ' && e.ctrlKey && !e.shiftKey) {
    clickFirst(replay_audio_class)
  } else if (key == 's') {
    clickFirst(strengthen_class) || clickFirst(practice_class) || clickFirst(untimed_class)
  } else if (key == 't') {
      clickFirst(test_class)
  } else if (key == 'h') {
      clickFirst(hint_class)
  } else if (key == 'd') {
    getEls('OzTZU')[1].click()
  } else if (key == 'r' && e.altKey) {
    clickFirst(home_class)
  } else if (key == 'tab') {
    if (e.shiftKey) {
      mouseoverCharacter(last_selected_index - 1)
    } else {
      mouseoverCharacter(last_selected_index + 1)
    }
    e.preventDefault()
  } else if (key == 'escape') {
    mouseoverCharacter(last_selected_index)
  } else if (e.keyCode >= 112 && e.keyCode <= 121) { // Fn
    const idx = e.keyCode - 112
    mouseoverCharacter(idx)
    e.preventDefault()
  } else if (e.keyCode >= 49 && e.keyCode <= 53) {
    const idx = (Number(key) + 10 - 1) % 10
    clickIndex(start_lesson_class, idx) || clickIndex(choice_class, idx)
  } else if (asdfg_arr.indexOf(key) != -1) {
    clickIndex(choice_class, asdfg_arr.indexOf(key))
  } else if (qwert_arr.indexOf(key) != -1) {
    clickIndex(choice_class, qwert_arr.indexOf(key) + 5)
  } else if (hjkl_arr.indexOf(key) != -1) {
    clickIndex(choice_class, hjkl_arr.indexOf(key) + 5)
  } else if (key == 'enter') {
      last_selected_index = -1
  } else {
      if (mouseover_active) {
        mouseoverCharacter(last_selected_index)
      }
  }
}
function startUntimed() {
  clickFirst(untimed_class)
  const els = getEls(test_out_class)
  if (els.length && els[0].innerText.includes('TEST OUT'))
      els[0].click()
}
setInterval(startUntimed, 100)
document.addEventListener('keydown', onKey)
window.localStorage["duo.session.coach_duo.last_shown_big_right_streak"] =
    window.localStorage["duo.session.coach_duo.last_shown_small_right_streak"] =
    window.localStorage["duo.session.coach_duo.last_shown_wrong_streak"] =
    Math.floor(new Date().valueOf() / 1000);