🏠 Home 

KhanHack

Khan Academy Answer Hack


Install this script?
  1. // ==UserScript==
  2. // @name KhanHack
  3. // @namespace https://greasyfork.org/users/783447
  4. // @version 6.1
  5. // @description Khan Academy Answer Hack
  6. // @author Logzilla6 - IlyTobias - Illusions
  7. // @match https://*.khanacademy.org/*
  8. // @icon https://i.ibb.co/K5g1KMq/Untitled-drawing-3.png
  9. // ==/UserScript==
  10. //ALL FOLLOWING CODE IS UNDER THE KHANHACK TRADEMARK. UNAUTHORIZED DISTRIBUTION CAN/WILL RESULT IN LEGAL ACTION
  11. //Note that KhanHack™ is an independent initiative and is not affiliated with or endorsed by Khan Academy. We respect the work of Khan Academy and its mission to provide free education, but KhanHack™ operates separately with its own unique goals.
  12. let mainMenu = document.createElement('div');
  13. mainMenu.id = 'mainMenu';
  14. mainMenu.style.position = 'fixed';
  15. mainMenu.style.bottom = '.5vw';
  16. mainMenu.style.left = '12vw';
  17. mainMenu.style.width = '300px';
  18. mainMenu.style.height = '400px';
  19. mainMenu.style.backgroundColor = '#123576';
  20. mainMenu.style.border = '3px solid #07152e';
  21. mainMenu.style.borderRadius = '20px';
  22. mainMenu.style.padding = '10px';
  23. mainMenu.style.color = "white";
  24. mainMenu.style.fontFamily = "Noto sans";
  25. mainMenu.style.fontWeight = "500";
  26. mainMenu.style.transition = "all 0.3s ease";
  27. mainMenu.style.zIndex = '1000';
  28. mainMenu.style.display = 'flex';
  29. mainMenu.style.flexDirection = 'column';
  30. let answerBlocks = [];
  31. let currentCombinedAnswer = '';
  32. let isGhostModeEnabled = false;
  33. let blockTick = 0;
  34. let firstAns;
  35. let secondAns;
  36. const setMainMenuContent = () => {
  37. mainMenu.innerHTML =`
  38. <div id="menuContent" style="display: flex; flex-direction: column; align-items: center; gap: 10px; opacity: 1; transition: opacity 0.5s ease; height: 100%;">
  39. <head>
  40. <img id="discordIcon" src="https://i.ibb.co/grF973h/discord.png" alt="Discord" style="position: absolute; left: 15px; top: 15px; width: 24px; height: 24px; opacity: 1; transition: opacity 0.5s ease; cursor: pointer;" />
  41. <img id="headerImage" src="https://i.ibb.co/h2GFJ5f/khanhack.png" style="width: 130px; opacity: 1; transition: opacity 0.5s ease;" />
  42. <img id="gearIcon" src="https://i.ibb.co/q0QVKGG/gearicon.png" alt="Settings" style="position: absolute; right: 15px; top: 15px; width: 24px; height: 24px; opacity: 1; transition: opacity 0.5s ease; cursor: pointer;" />
  43. </head>
  44. <div id="answerList" class="answerList"></div>
  45. <div id="copyText2" class="copyText2">Click to copy</div>
  46. </div>
  47. <img id="toggleButton" src="https://i.ibb.co/RpqPcR1/hamburger.png" class="toggleButton">
  48. <img id="clearButton" src="https://i.ibb.co/bz0jPmc/Pngtree-white-refresh-icon-4543883.png" style="width: 34px; height: 34px; bottom: 0px; right: 0px; position: absolute; cursor: pointer;">
  49. <style>
  50. @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
  51. .toggleButton {
  52. position: absolute;
  53. bottom: 7px;
  54. left: 7px;
  55. height: 20px;
  56. width: 20px;
  57. cursor: pointer;
  58. }
  59. .answerList {
  60. width: 100%;
  61. display: flex;
  62. flex-direction: column;
  63. gap: 10px;
  64. flex-grow: 1;
  65. max-height: calc(100% - 100px);
  66. overflow-y: scroll;
  67. padding-bottom: 10px;
  68. }
  69. .block {
  70. width: 280px;
  71. height: auto;
  72. background-color: #f0f0f0;
  73. padding: 10px;
  74. border-radius: 10px;
  75. opacity: 1;
  76. display: flex;
  77. justify-content: center;
  78. align-items: center;
  79. margin-left: auto;
  80. margin-right: auto;
  81. transition: 0.2s ease;
  82. word-wrap: break-word;
  83. }
  84. .block:hover {
  85. background-color: #d9d7d7;
  86. }
  87. .answerList:hover + .copyText2 {
  88. opacity: 100;
  89. }
  90. .answer {
  91. margin: 0;
  92. text-align: center;
  93. color: black;
  94. font-family: "Noto Sans";
  95. font-weight: 500;
  96. }
  97. .imgBlock img {
  98. width: 250px;
  99. border-radius: 10px;
  100. }
  101. .copied {
  102. margin-top: -200px;
  103. }
  104. #answerList::-webkit-scrollbar {
  105. display: none;
  106. }
  107. #answerList {
  108. -ms-overflow-style: none;
  109. scrollbar-width: none;
  110. }
  111. .copyText2 {
  112. text-align: center;
  113. padding-top: 10px;
  114. left: 50%;
  115. font-size: 15px;
  116. opacity: 0;
  117. transition: opacity 0.2s ease, font-size 0.1s ease;
  118. }
  119. .ansVal {
  120. }
  121. </style>
  122. `;
  123. addToggle();
  124. addSettings();
  125. addDiscord();
  126. addClear();
  127. const answerList = document.getElementById('answerList');
  128. if (isGhostModeEnabled) {
  129. enableGhostMode();
  130. }
  131. };
  132. let isMenuVisible = true;
  133. const addToggle = () => {
  134. document.getElementById('toggleButton').addEventListener('click', function() {
  135. const clearButton = document.getElementById('clearButton');
  136. if (isMenuVisible) {
  137. mainMenu.style.height = '15px';
  138. mainMenu.style.width = '15px';
  139. document.getElementById('menuContent').style.opacity = '0';
  140. clearButton.style.opacity = '0';
  141. setTimeout(() => {
  142. document.getElementById('menuContent').style.display = 'none';
  143. clearButton.style.display = 'none';
  144. }, 50);
  145. } else {
  146. mainMenu.style.height = '400px';
  147. mainMenu.style.width = '300px';
  148. document.getElementById('menuContent').style.display = 'flex';
  149. clearButton.style.display = 'block';
  150. setTimeout(() => {
  151. document.getElementById('menuContent').style.opacity = '1';
  152. clearButton.style.opacity = '1';
  153. }, 100);
  154. }
  155. isMenuVisible = !isMenuVisible;
  156. });
  157. };
  158. const addSettings = () => {
  159. document.getElementById('gearIcon').addEventListener('click', function() {
  160. let saveHtml = document.getElementById('mainMenu').innerHTML
  161. mainMenu.innerHTML = `
  162. <div id="settingsContent" style="display: flex; flex-direction: column; align-items: center; position: relative; opacity: 1; transition: opacity 0.5s ease;">
  163. <img id="backArrow" src="https://i.ibb.co/Jt4qrD7/pngwing-com-1.png" alt="Back" style="position: absolute; left: 7px; top: 3px; width: 24px; height: 24px; opacity: 1; transition: opacity 0.5s ease; cursor: pointer;" />
  164. <h3 style="margin: 0; text-align: center; color: white; font-family: Noto sans; font-weight: 500;">Settings Menu</h3>
  165. <p style="text-align: center; color: white; font-family: Noto sans; margin-top: 15px;">
  166. Ghost Mode: <input type="checkbox" id="ghostModeToggle" class="ghostToggle2" ${isGhostModeEnabled ? 'checked' : ''}>
  167. </p>
  168. <p style="text-align: center; color: white; font-family: Noto sans; margin-top: 15px;">Auto Answer: BETA</p>
  169. <p style="text-align: center; color: white; font-family: Noto sans; margin-top: 15px;">Point Farmer: BETA</p>
  170. <p style="text-align: center; color: white; font-family: Noto sans; margin-top: 30px; font-size: 25px;">Beta Access In Discord</p>
  171. <p style="text-align: center; color: white; font-family: Noto sans; margin-top: 80px;">KhanHack | 6.0</p>
  172. <style>
  173. @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
  174. .ghostToggle {
  175. width: 20px;
  176. height: 20px;
  177. background-color: white;
  178. border-radius: 50%;
  179. vertical-align: middle;
  180. border: 2px solid #07152e;
  181. appearance: none;
  182. -webkit-appearance: none;
  183. outline: none;
  184. cursor: pointer;
  185. transition: 0.2s ease;
  186. }
  187. .ghostToggle:checked {
  188. background-color: #2967d9;
  189. }
  190. </style>
  191. </div>
  192. `;
  193. document.getElementById('backArrow').addEventListener('click', () => {mainMenu.innerHTML = saveHtml; addSettings(); addToggle(); addDiscord(); addClear();});
  194. document.getElementById('ghostModeToggle').addEventListener('change', function() {
  195. isGhostModeEnabled = this.checked;
  196. if (isGhostModeEnabled) {
  197. enableGhostMode();
  198. } else {
  199. disableGhostMode();
  200. }
  201. });
  202. });
  203. };
  204. const enableGhostMode = () => {
  205. mainMenu.style.opacity = '0';
  206. mainMenu.addEventListener('mouseenter', handleMouseEnter);
  207. mainMenu.addEventListener('mouseleave', handleMouseLeave);
  208. };
  209. const disableGhostMode = () => {
  210. mainMenu.style.opacity = '1';
  211. mainMenu.removeEventListener('mouseenter', handleMouseEnter);
  212. mainMenu.removeEventListener('mouseleave', handleMouseLeave);
  213. };
  214. const handleMouseEnter = () => {
  215. mainMenu.style.opacity = '1';
  216. };
  217. const handleMouseLeave = () => {
  218. mainMenu.style.opacity = '0';
  219. };
  220. const addDiscord = () => {
  221. document.getElementById('discordIcon').addEventListener('click', function() {
  222. window.open('https://discord.gg/khanhack', '_blank');
  223. });
  224. };
  225. const addClear = () => {
  226. document.getElementById('clearButton').addEventListener('click', function() {
  227. location.reload();
  228. });
  229. };
  230. const script = document.createElement("script");
  231. script.src = "https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js";
  232. document.head.appendChild(script);
  233. const katexStyle = document.createElement("link");
  234. katexStyle.rel = "stylesheet";
  235. katexStyle.href = "https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css";
  236. document.head.appendChild(katexStyle);
  237. const getCurrentQuestion = () => {
  238. const container = document.querySelector(`div[data-testid="content-library-footer"]`)
  239. let firstChar = container.querySelectorAll("div")[5].children[0].innerText.charAt(0)
  240. let lastChar = container.querySelectorAll("div")[5].children[0].innerText.slice(-1)
  241. if(firstChar == lastChar-1) {
  242. console.log(true)
  243. container.querySelectorAll("button")[3].onclick = function() {;
  244. firstAns = document.getElementById(`blockNum${blockTick-1}`)
  245. console.log(firstAns)
  246. secondAns = document.getElementById(`blockNum${blockTick}`)
  247. secondAns.style.opacity = "100%";
  248. firstAns.remove()
  249. answerBlocks.shift()
  250. }
  251. } else {
  252. console.log(false)
  253. }
  254. }
  255. const addNewAnswerBlock = (answer, imgSrc, isImg) => {
  256. const answerList = document.getElementById('answerList');
  257. const block = document.createElement('div');
  258. blockTick ++
  259. //console.log(' blockTick: ' + blockTick)
  260. if(isImg == true) {
  261. block.className = 'block imgBlock';
  262. const img = document.createElement('img');
  263. img.src = imgSrc;
  264. block.id = `blockNum${blockTick}`
  265. block.innerHTML = `${answer}`;
  266. block.style.display = "inline-block"
  267. block.style.color = "black";
  268. block.appendChild(img);
  269. answerList.appendChild(block);
  270. answerBlocks.push({ type: 'image', content: block.id });
  271. //console.log('num: ' + block.id)
  272. }
  273. else {
  274. block.className = 'block no-select';
  275. block.id = `blockNum${blockTick}`
  276. block.style.cursor = "pointer";
  277. block.addEventListener("click", () => {
  278. console.log('clicked')
  279. navigator.clipboard.writeText(answer);
  280. });
  281. const ansVal = document.createElement('a');
  282. ansVal.className = 'answer';
  283. const latexPattern = /\\frac|\\sqrt|\\times|\\cdot|\\degree|\\dfrac|\test|\\vec\\leq|\\left|\\right|\^|\$|\{|\}/;
  284. if (latexPattern.test(answer)) {
  285. ansVal.innerHTML = '';
  286. katex.render(answer, ansVal)
  287. } else {
  288. ansVal.innerHTML = `${answer}`;
  289. }
  290. ansVal.style.fontSize = "16px";
  291. block.appendChild(ansVal);
  292. answerList.appendChild(block);
  293. answerBlocks.push({ type: 'text', content: block.id });
  294. //console.log('num: ' + block.id)
  295. }
  296. const runList = () => {
  297. if(answerBlocks.length == 3) {
  298. //console.log(`length is ${answerBlocks.length}`)
  299. firstAns = document.getElementById(`blockNum${blockTick-2}`)
  300. secondAns = document.getElementById(`blockNum${blockTick-1}`)
  301. secondAns.style.opacity = "100%";
  302. firstAns.remove()
  303. answerBlocks.shift()
  304. getCurrentQuestion()
  305. //console.log(`shifted is ${answerBlocks.length}`)
  306. runList()
  307. } else if(answerBlocks.length == 2) {
  308. //console.log(`length is ${answerBlocks.length}`)
  309. firstAns = document.getElementById(`blockNum${blockTick-1}`)
  310. secondAns = document.getElementById(`blockNum${blockTick}`)
  311. if(secondAns.style.opacity == "0%") {
  312. firstAns.remove()
  313. answerBlocks.shift()
  314. secondAns.style.opacity = "100%";
  315. } else{
  316. secondAns.style.opacity = "0%";
  317. }
  318. }
  319. }
  320. runList()
  321. }
  322. document.body.appendChild(mainMenu);
  323. setMainMenuContent();
  324. let originalJson = JSON.parse;
  325. JSON.parse = function (jsonString) {
  326. let parsedData = originalJson(jsonString);
  327. try {
  328. if (parsedData.data && parsedData.data.assessmentItem && parsedData.data.assessmentItem.item) {
  329. let itemData = JSON.parse(parsedData.data.assessmentItem.item.itemData);
  330. let hasGradedWidget = Object.values(itemData.question.widgets).some(widget => widget.graded === true);
  331. if (hasGradedWidget) {
  332. for (let widgetKey in itemData.question.widgets) {
  333. let widget = itemData.question.widgets[widgetKey];
  334. console.log(widget.type)
  335. switch (widget.type) {
  336. case "numeric-input":
  337. handleNumeric(widget);
  338. break;
  339. case "radio":
  340. handleRadio(widget);
  341. break;
  342. case "expression":
  343. handleExpression(widget);
  344. break;
  345. case "dropdown":
  346. handleDropdown(widget);
  347. break;
  348. case "interactive-graph":
  349. handleIntGraph(widget);
  350. break;
  351. case "grapher":
  352. handleGrapher(widget);
  353. break;
  354. case "input-number":
  355. handleInputNum(widget);
  356. break;
  357. case "matcher":
  358. handleMatcher(widget);
  359. break;
  360. case "categorizer":
  361. handleCateg(widget);
  362. break;
  363. case "label-image":
  364. handleLabel(widget);
  365. break;
  366. case "matrix":
  367. handleMatrix(widget);
  368. break;
  369. default:
  370. console.log("Unknown widget: " + widget.type);
  371. break;
  372. }
  373. }
  374. if (currentCombinedAnswer.trim() !== '') {
  375. if(currentCombinedAnswer.slice(-4) == '<br>') {
  376. addNewAnswerBlock(currentCombinedAnswer.slice(0, -4), null, false)
  377. currentCombinedAnswer = '';
  378. } else {
  379. addNewAnswerBlock(currentCombinedAnswer, null, false)
  380. currentCombinedAnswer = '';
  381. }
  382. }
  383. }
  384. }
  385. } catch (error) {
  386. console.log("Error parsing JSON:", error);
  387. }
  388. return parsedData;
  389. };
  390. function cleanLatexExpression(answer) {
  391. return answer
  392. .replace('begin{align}', 'begin{aligned}')
  393. .replace('end{align}', 'end{aligned}')
  394. .replace(/\$/g, '');
  395. }
  396. function handleRadio(widget) {
  397. let corAns = widget.options.choices.filter(item => item.correct === true).map(item => item.content);
  398. let ansArr = [];
  399. let isNone = widget.options.choices.filter(item => item.isNoneOfTheAbove === true && item.correct === true)
  400. if (isNone.length > 0) {
  401. currentCombinedAnswer += "None of the above";
  402. return;
  403. }
  404. console.log(corAns)
  405. corAns.forEach(answer => {
  406. const hasGraphie = answer.includes('web+graphie')
  407. const hasNotGraphie = answer.includes('![')
  408. if(hasGraphie || hasNotGraphie == true) {
  409. if(hasGraphie == true) {
  410. const split = answer.split('](web+graphie');
  411. const text = split[0].slice(2)
  412. const midUrl = split[1].split(')')[0];
  413. const finalUrl = 'https' + midUrl + '.svg';
  414. addNewAnswerBlock(text, finalUrl, true);
  415. } else if(hasNotGraphie == true) {
  416. const finalUrl = answer.slice(answer.indexOf('https'), -1)
  417. addNewAnswerBlock(null, finalUrl, true);
  418. }
  419. } else {
  420. let cleaned = cleanLatexExpression(answer)
  421. ansArr.push(cleaned)
  422. console.log(cleaned)
  423. }
  424. })
  425. if(ansArr.length) {
  426. currentCombinedAnswer += ansArr.join()
  427. }
  428. }
  429. function handleLabel(widget) {
  430. let corAns = widget.options.markers.filter(item => item.answers).map(item => item.answers)
  431. let labels = widget.options.markers.filter(item => item.label).map(item => item.label)
  432. let ansArr = []
  433. corAns.forEach((answer, index) => {
  434. if(labels == 0) {
  435. let cleaned = cleanLatexExpression(answer.toString());
  436. ansArr.push(cleaned)
  437. } else {
  438. let cleaned = cleanLatexExpression(answer.toString());
  439. let finLabel = labels[index].replace('Point ', '').replace(/[.]/g, '').trim() || "";
  440. let labeledAnswer = `${finLabel}: ${cleaned}`;
  441. ansArr.push(labeledAnswer)
  442. }
  443. })
  444. if(ansArr.length) {
  445. currentCombinedAnswer += ansArr.join("|")
  446. }
  447. }
  448. function handleNumeric(widget) {
  449. const numericAnswer = widget.options.answers[0].value;
  450. currentCombinedAnswer += `${numericAnswer}<br>`;
  451. }
  452. function handleExpression(widget) {
  453. let expressionAnswer = widget.options.answerForms[0].value;
  454. let cleaned = cleanLatexExpression(expressionAnswer)
  455. console.log(expressionAnswer)
  456. currentCombinedAnswer += ` ${cleaned} `;
  457. }
  458. function handleDropdown(widget) {
  459. let content = widget.options.choices.filter(item => item.correct === true).map(item => item.content);
  460. currentCombinedAnswer += ` ${content[0]} `;
  461. }
  462. function handleIntGraph(widget) {
  463. let coords = widget.options.correct.coords;
  464. let validCoords = coords.filter(coord => coord !== undefined);
  465. currentCombinedAnswer += ` ${validCoords.join(' | ')} `;
  466. }
  467. function handleInputNum(widget) {
  468. let inputNumAnswer = widget.options.value;
  469. console.log(inputNumAnswer)
  470. currentCombinedAnswer += ` ${inputNumAnswer} `;
  471. }
  472. function handleMatcher(widget) {
  473. let matchAnswer = widget.options.right;
  474. let cleaned = cleanLatexExpression(matchAnswer)
  475. currentCombinedAnswer += ` ${matchAnswer} `;
  476. }
  477. function handleGrapher(widget) {
  478. let coords = widget.options.correct.coords;
  479. currentCombinedAnswer += ` ${coords.join(' | ')} `;
  480. }
  481. function handleCateg(widget) {
  482. let values = widget.options.values;
  483. let categories = widget.options.categories;
  484. let labeledValues = values.map(value => categories[value]);
  485. currentCombinedAnswer += ` ${labeledValues} `
  486. }
  487. function handleMatrix(widget) {
  488. let arrs = widget.options.answers;
  489. currentCombinedAnswer += ` ${arrs.join(' | ')} `
  490. }