Skip to content

Instantly share code, notes, and snippets.

@JasoonS
Last active April 15, 2020 09:40
Show Gist options
  • Save JasoonS/661b1e9c4207f5584bfebccc7fa29599 to your computer and use it in GitHub Desktop.
Save JasoonS/661b1e9c4207f5584bfebccc7fa29599 to your computer and use it in GitHub Desktop.
Duolingo 'no mouse needed' tampermonkey
// ==UserScript==
// @name Duolingo mods
// @namespace http://tampermonkey.net/
// @version 1.1
// @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==
/*
Summary:
add this to your userscripts (eg tampermonkey).
Keys:
Hover over meanings: ctrl + num
Select item: num
remove item: shift + num
Remove last item: '-'
You can modify these shortcuts as you want in the switch statement in the `onKey(e)` function.
(for anyone else ready I use a modified keyboard based on dvorak, so please feel free to change these defaults to your convenience)
Duolingo periodically updates their stylesheets. So this won't work forever. You need to update the 3 constants to find the correct class.
Tested only on Firefox.
*/
// constants
const answerBoxClass = '_3vVWl'
const choice_class = '_1HcF0'
const mouseover_character_class = 'QXuFD'
let removeNumbers = []
let removeSelectedNumbers = []
// variables
let last_selected_index = -1
let mouseover_active = false
// helpers
const addNumbersOnButtons = (els) => {
return els.map((el, i) => {
const num = document.createElement( 'div' )
num.textContent = '#' +(i+1)
el.appendChild(num)
return () => el.removeChild(num)
})
}
const removeAllNumbers = (removeNumbers) => {
removeNumbers.map(remove => remove())
}
const updateUnusedNumbers = () => {
const els = [...document.getElementsByClassName(choice_class)].filter(
(node) =>
!(document.getElementsByClassName(answerBoxClass)[0].contains(node))
).filter(node =>
!node.disabled
)
removeAllNumbers(removeNumbers)
removeNumbers = addNumbersOnButtons(els)
}
const updateUsedNumbers = () => {
const els = [...document.getElementsByClassName(choice_class)].filter(
(node) =>
(document.getElementsByClassName(answerBoxClass)[0].contains(node))
)
removeAllNumbers(removeSelectedNumbers)
removeSelectedNumbers = addNumbersOnButtons(els)
}
const updateAllNumbers = () => {
updateUsedNumbers()
updateUnusedNumbers()
}
// This is a bit hacky... But that isn't too much of a problem, js is fast :P It should only need to run when a new pages loads
setInterval(updateAllNumbers, 500);
function unusedItem(idx) {
const els = [...document.getElementsByClassName(choice_class)].filter(
(node) =>
!(document.getElementsByClassName(answerBoxClass)[0].contains(node))
).filter(node =>
!node.disabled
)
if (els.length > idx) {
els[idx].click()
updateAllNumbers()
return true
}
return false
}
function removeLastSelected() {
const els = [...document.getElementsByClassName(choice_class)].filter(
(node) =>
(document.getElementsByClassName(answerBoxClass)[0].contains(node))
)
if (els.length > 0) {
els[els.length - 1].click()
updateAllNumbers()
return true
}
return false
}
function selectedItem(idx) {
const els = [...document.getElementsByClassName(choice_class)].filter(
(node) =>
(document.getElementsByClassName(answerBoxClass)[0].contains(node))
)
if (els.length > idx) {
els[idx].click()
updateAllNumbers()
return true
}
return false
}
const getEls = class_name => document.getElementsByClassName(class_name)
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) {
const key = e.key.toLowerCase()
if (key == 'escape') {
mouseoverCharacter(last_selected_index)
} else if (key == '*' || (key == '1' && e.ctrlKey)) { // Fn
const idx = 0
mouseoverCharacter(idx)
} else if (key == ')' || (key == '2' && e.ctrlKey)) { // Fn
const idx = 1
mouseoverCharacter(idx)
} else if (key == '+' || (key == '3' && e.ctrlKey)) { // Fn
const idx = 2
mouseoverCharacter(idx)
} else if (key == ']' || (key == '4' && e.ctrlKey)) { // Fn
const idx = 3
mouseoverCharacter(idx)
} else if (key == '}' || (key == '5' && e.ctrlKey)) { // Fn
const idx = 4
mouseoverCharacter(idx)
} else if (key == '{' || (key == '6' && e.ctrlKey)) { // Fn
const idx = 5
mouseoverCharacter(idx)
} else if (key == '[' || (key == '7' && e.ctrlKey)) { // Fn
const idx = 6
mouseoverCharacter(idx)
} else if (key == '%' || (key == '8' && e.ctrlKey)) { // Fn
const idx = 7
mouseoverCharacter(idx)
} else if (key == '!' || (key == '9' && e.ctrlKey)) { // Fn
const idx = 8
mouseoverCharacter(idx)
} else if (key == '~' || (key == '0' && e.ctrlKey)) { // Fn
const idx = 9
mouseoverCharacter(idx)
} else if (key == '-') {
removeLastSelected()
} else if (e.shiftKey && e.keyCode >= 48 && e.keyCode <= 58) {
const idx = 9 - ((10 - Number(key)) % 10)
selectedItem(idx)
} else if (e.keyCode >= 48 && e.keyCode <= 58) {
const idx = 9 - ((10 - Number(key)) % 10)
unusedItem(idx)
} else {
if (mouseover_active) {
mouseoverCharacter(last_selected_index)
}
}
}
document.addEventListener('keydown', onKey)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment