KhanHack

Khan Answer Hack

目前為 2024-10-26 提交的版本,檢視 最新版本

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