Last active
April 15, 2020 09:40
-
-
Save JasoonS/661b1e9c4207f5584bfebccc7fa29599 to your computer and use it in GitHub Desktop.
Duolingo 'no mouse needed' tampermonkey
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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