KhanHack

Khan Academy Answer Hack

当前为 2024-09-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name KhanHack
  3. // @namespace https://greasyfork.org/users/783447
  4. // @version 5.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. let mainMenu = document.createElement('div');
  12. mainMenu.id = 'mainMenu';
  13. mainMenu.style.position = 'fixed';
  14. mainMenu.style.bottom = '.5vw';
  15. mainMenu.style.left = '19vw';
  16. mainMenu.style.width = '300px';
  17. mainMenu.style.height = '400px';
  18. mainMenu.style.backgroundColor = '#123576';
  19. mainMenu.style.border = '3px solid #07152e';
  20. mainMenu.style.borderRadius = '20px';
  21. mainMenu.style.padding = '10px';
  22. mainMenu.style.color = "white";
  23. mainMenu.style.fontFamily = "Noto sans";
  24. mainMenu.style.fontWeight = "500";
  25. mainMenu.style.transition = "all 0.3s ease";
  26. mainMenu.style.zIndex = '1000';
  27. mainMenu.style.display = 'flex';
  28. mainMenu.style.flexDirection = 'column';
  29.  
  30. let copied = document.createElement('div');
  31. copied.id = 'copyText';
  32. copied.style.position = 'fixed';
  33. copied.style.bottom = '17.5vw';
  34. copied.style.left = '22.2vw';
  35. copied.style.width = '150px';
  36. copied.style.height = 'auto';
  37. copied.style.backgroundColor = '#123576';
  38. copied.style.border = '3px solid #07152e';
  39. copied.style.borderRadius = '13px';
  40. copied.style.color = "white";
  41. copied.style.fontFamily = "Noto sans";
  42. copied.style.fontWeight = "500";
  43. copied.style.transition = "all 0.15s ease";
  44. copied.style.padding = "5px";
  45. copied.style.opacity = "0";
  46.  
  47. let answerBlocks = [];
  48. let currentCombinedAnswer = '';
  49.  
  50. const setCopiedContent = () => {
  51. copied.innerHTML = `
  52. <div style="display: flex; flex-direction: column; align-items: center; text-align: center; position: relative; gap: 10px; opacity: 1; transition: opacity 0.5s ease;">
  53. <a>Successfully Copied!</a>
  54. </div>
  55.  
  56. <style>
  57. @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
  58. </style>
  59. `;
  60. }
  61.  
  62. const copyFlash = () => {
  63. copied.style.opacity = "1";
  64. setTimeout(() => {
  65. copied.style.opacity = "0";
  66. }, 1300);
  67. };
  68.  
  69. const setMainMenuContent = () => {
  70. mainMenu.innerHTML =`
  71. <div id="menuContent" style="display: flex; flex-direction: column; align-items: center; gap: 10px; opacity: 1; transition: opacity 0.5s ease; height: 100%;">
  72. <head>
  73. <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;" />
  74. <img id="headerImage" src="https://i.ibb.co/pX592fL/khanhack.png" style="width: 170px; opacity: 1; transition: opacity 0.5s ease;" />
  75. <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;" />
  76. </head>
  77. <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>
  78. </div>
  79.  
  80. <img id="toggleButton" src="https://i.ibb.co/RpqPcR1/hamburger.png" class="toggleButton">
  81. <img id="clearButton" src="https://i.ibb.co/VYs1BPQ/can.png" style="width: 20px; height: 20px; bottom: 12px; right: 8px; position: absolute; cursor: pointer;">
  82.  
  83. <style>
  84. @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
  85.  
  86. .toggleButton {
  87. position: absolute;
  88. bottom: 7px;
  89. left: 7px;
  90. height: 20px;
  91. width: 20px;
  92. cursor: pointer;
  93. }
  94.  
  95. .block {
  96. width: 280px; /* Fixed width for the answer blocks */
  97. min-height: 40px; /* Ensure the minimum height stays consistent */
  98. height: auto;
  99. background-color: #f0f0f0;
  100. padding: 10px;
  101. border-radius: 10px;
  102. opacity: 1;
  103. display: flex;
  104. justify-content: center;
  105. transition: opacity 0.5s ease;
  106. align-items: center;
  107. margin-left: auto;
  108. margin-right: auto;
  109. transition: 0.2s ease;
  110. word-wrap: break-word; /* Ensure text wraps properly */
  111. }
  112.  
  113. .block:hover {
  114. background-color: #d9d7d7;
  115. }
  116.  
  117. .answer {
  118. margin: 0;
  119. text-align: center;
  120. color: black;
  121. font-family: "Noto Sans";
  122. font-weight: 500;
  123. }
  124.  
  125. .imgBlock img {
  126. width: 250px;
  127. border-radius: 10px;
  128. }
  129.  
  130. .copied {
  131. margin-top: -200px;
  132. }
  133.  
  134. /* Hide scrollbar */
  135. #answerList::-webkit-scrollbar {
  136. display: none;
  137. }
  138.  
  139. #answerList {
  140. -ms-overflow-style: none; /* Internet Explorer 10+ */
  141. scrollbar-width: none; /* Firefox */
  142. }
  143. </style>
  144. `;
  145.  
  146. addToggle();
  147. addSettings();
  148. addDiscord();
  149. addClear();
  150.  
  151. const script = document.createElement("script");
  152. script.src = "https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js";
  153. document.head.appendChild(script);
  154.  
  155. const katexStyle = document.createElement("link");
  156. katexStyle.rel = "stylesheet";
  157. katexStyle.href = "https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css";
  158. document.head.appendChild(katexStyle);
  159.  
  160. const answerList = document.getElementById('answerList');
  161. answerList.innerHTML = '';
  162.  
  163. answerBlocks.forEach(block => {
  164. if (block.type === 'text') {
  165. addAnswerBlock(block.content, false);
  166. } else if (block.type === 'image') {
  167. addImgAnswerBlock(block.content, false);
  168. }
  169. });
  170. };
  171.  
  172. const addAnswerBlock = (answer, save = true) => {
  173. const answerList = document.getElementById('answerList');
  174. const block = document.createElement('div');
  175. block.className = 'block no-select';
  176. block.style.cursor = "pointer";
  177. block.addEventListener("click", () => {
  178. navigator.clipboard.writeText(answer);
  179. copyFlash();
  180. });
  181.  
  182. const p = document.createElement('p');
  183. p.className = 'answer';
  184.  
  185. const latexPattern = /\\frac|\\sqrt|\\times|\\cdot|\\left|\\right|\^|\$|\{|\}/;
  186. if (latexPattern.test(answer)) {
  187. p.innerHTML = `Answer: `;
  188. katex.render(answer, p, {
  189. throwOnError: false
  190. });
  191. } else {
  192. p.innerHTML = `<span>Answer: </span> ${answer}`;
  193. }
  194.  
  195. p.style.fontFamily = '"Noto Sans", sans-serif';
  196. p.style.fontWeight = "500";
  197. p.style.fontStyle = "normal";
  198.  
  199. block.appendChild(p);
  200. answerList.appendChild(block);
  201.  
  202. if (save) {
  203. answerBlocks.push({ type: 'text', content: answer });
  204. }
  205. };
  206.  
  207. const addImgAnswerBlock = (imgSrc, save = true) => {
  208. const answerList = document.getElementById('answerList');
  209. const block = document.createElement('div');
  210. block.className = 'block imgBlock';
  211.  
  212. const img = document.createElement('img');
  213. img.src = imgSrc;
  214.  
  215. block.appendChild(img);
  216. answerList.appendChild(block);
  217.  
  218. if (save) {
  219. answerBlocks.push({ type: 'image', content: imgSrc });
  220. }
  221. };
  222.  
  223. let isMenuVisible = true;
  224. const addToggle = () => {
  225. document.getElementById('toggleButton').addEventListener('click', function() {
  226. const clearButton = document.getElementById('clearButton');
  227. if (isMenuVisible) {
  228. mainMenu.style.height = '15px';
  229. mainMenu.style.width = '15px';
  230. document.getElementById('menuContent').style.opacity = '0';
  231. clearButton.style.opacity = '0';
  232. setTimeout(() => {
  233. document.getElementById('menuContent').style.display = 'none';
  234. clearButton.style.display = 'none';
  235. }, 50);
  236. } else {
  237. mainMenu.style.height = '400px';
  238. mainMenu.style.width = '300px';
  239. document.getElementById('menuContent').style.display = 'flex';
  240. clearButton.style.display = 'block';
  241. setTimeout(() => {
  242. document.getElementById('menuContent').style.opacity = '1';
  243. clearButton.style.opacity = '1';
  244. }, 100);
  245. }
  246. isMenuVisible = !isMenuVisible;
  247. });
  248. };
  249.  
  250. const addSettings = () => {
  251. document.getElementById('gearIcon').addEventListener('click', function() {
  252. mainMenu.innerHTML = `
  253. <div id="settingsContent" style="display: flex; flex-direction: column; align-items: center; position: relative; opacity: 1; transition: opacity 0.5s ease;">
  254. <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;" />
  255.  
  256. <h3 style="margin: 0; text-align: center; color: white; font-family: Noto sans; font-weight: 500;">Settings Menu</h3>
  257. <p style="text-align: center; color: white; font-family: Noto sans; margin-top: 15px;">Ghost Mode: (Coming Soon)</p>
  258. <p style="text-align: center; color: white; font-family: Noto sans; margin-top: 15px;">Auto Answer: (Coming Soon)</p>
  259. <p style="text-align: center; color: white; font-family: Noto sans; margin-top: 15px;">Point Farmer: (Coming Soon)</p>
  260. <p style="text-align: center; color: white; font-family: Noto sans; margin-top: 30px; font-size: 20px;">Join the discord for free beta access</p>
  261. <p style="text-align: center; color: white; font-family: Noto sans; margin-top: 80px;">KhanHack™</p>
  262.  
  263. <style>
  264. @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
  265. </style>
  266. </div>
  267. `;
  268. document.getElementById('backArrow').addEventListener('click', setMainMenuContent);
  269. });
  270. };
  271.  
  272. const addDiscord = () => {
  273. document.getElementById('discordIcon').addEventListener('click', function() {
  274. window.open('https://discord.gg/khanhack', '_blank');
  275. });
  276. };
  277.  
  278. const addClear = () => {
  279. document.getElementById('clearButton').addEventListener('click', function() {
  280. location.reload();
  281. });
  282. };
  283.  
  284. document.body.appendChild(mainMenu);
  285. document.body.appendChild(copied);
  286. setMainMenuContent();
  287. setCopiedContent();
  288.  
  289. let originalJson = JSON.parse;
  290. JSON.parse = function (jsonString) {
  291. let parsedData = originalJson(jsonString);
  292. try {
  293. let itemData = JSON.parse(parsedData.data.assessmentItem.item.itemData);
  294.  
  295. for (let widgetKey in itemData.question.widgets) {
  296. let widget = itemData.question.widgets[widgetKey];
  297. switch (widget.type) {
  298. case "numeric-input":
  299. handleNumeric(widget);
  300. break;
  301. case "radio":
  302. handleRadio(widget);
  303. break;
  304. case "expression":
  305. handleExpression(widget);
  306. break;
  307. case "dropdown":
  308. handleDropdown(widget);
  309. break;
  310. default:
  311. console.log("Unknown widget: " + widget.type);
  312. break;
  313. }
  314. }
  315. if (currentCombinedAnswer.trim() !== '') {
  316. addAnswerBlock(currentCombinedAnswer.trim());
  317. currentCombinedAnswer = '';
  318. }
  319. } catch (error) {
  320. console.log("Error parsing JSON:", error);
  321. }
  322. return parsedData;
  323. };
  324.  
  325. function cleanLatexExpression(answer) {
  326. return answer
  327. .replace(/\\times/g, '×')
  328. .replace(/\\frac{([^}]*)}{([^}]*)}/g, '($1/$2)')
  329. .replace(/\\cdot/g, '⋅')
  330. .replace(/\\left|\\right/g, '')
  331. .replace(/[\$]/g, '')
  332. .replace(/\^/g, '^')
  333. .replace(/\\(?:[a-zA-Z]+)/g, '')
  334. .replace(/(?<!\\){|}/g, '');
  335. }
  336.  
  337. function handleRadio(widget) {
  338. let content = widget.options.choices.filter(item => item.correct === true).map(item => item.content);
  339. let answersArray = [];
  340. let hasImage = false;
  341. let imageUrl = null;
  342. let noneAbove = widget.options.choices.filter(item => item.isNoneOfTheAbove === true && item.correct === true);
  343.  
  344. if (noneAbove.length > 0) {
  345. currentCombinedAnswer += "None of the above | ";
  346. return;
  347. }
  348.  
  349. content.forEach(answer => {
  350. const regex = answer.match(/:\/\/(.*?)\)/);
  351.  
  352. if (regex) {
  353. const finalImg = "https" + regex[0].slice(0, -1) + ".svg";
  354. hasImage = true;
  355. imageUrl = finalImg;
  356. } else {
  357. const cleanedAnswer = cleanLatexExpression(answer);
  358. answersArray.push(cleanedAnswer);
  359. }
  360. });
  361.  
  362. if (answersArray.length) {
  363. currentCombinedAnswer += answersArray.join(' | ') + " | ";
  364. }
  365.  
  366. if (hasImage && imageUrl) {
  367. addImgAnswerBlock(imageUrl);
  368. }
  369. }
  370.  
  371. function handleNumeric(widget) {
  372. const numericAnswer = widget.options.answers[0].value;
  373. currentCombinedAnswer += `${numericAnswer} `;
  374. }
  375.  
  376. function handleExpression(widget) {
  377. let expressionAnswer = widget.options.answerForms[0].value;
  378. currentCombinedAnswer += `${expressionAnswer} `;
  379. }
  380.  
  381. function handleDropdown(widget) {
  382. let content = widget.options.choices.filter(item => item.correct === true).map(item => item.content);
  383. currentCombinedAnswer += `${content[0]} `;
  384. }