KhanHack

Khan Academy Answer Hack

目前为 2025-02-08 提交的版本。查看 最新版本

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址