KhanHack

Sloppy lazybones anti-homework production

  1. // ==UserScript==
  2. // @name KhanHack
  3. // @namespace http://khan.dyntech.cc
  4. // @version 6.0
  5. // @description Sloppy lazybones anti-homework production
  6. // @author DynTech
  7. // @match *://*.khanacademy.org/*
  8. // @oicon https://dyntech.cc/favicon?q=khan.dyntech.cc
  9. // @icon https://cdn.dyntech.cc/r/khanhack.png
  10. // @grant none
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16. // This FREE script was extracted from a $7.99/month extension. You're welcome.
  17. (function() {
  18. const originalFetch = window.fetch;
  19.  
  20. // Check for an existing container and restore it if minimized
  21. let mainContainer = document.getElementById('questionDataContainer');
  22. if (mainContainer) {
  23. // If the container is minimized, restore it
  24. if (mainContainer.classList.contains('minimized')) {
  25. mainContainer.classList.remove('minimized');
  26. const restoreButton = document.getElementById('restoreButton');
  27. restoreButton.classList.remove('minimized');
  28. const minimizeButton = document.getElementById('minimizeButton');
  29. minimizeButton.style.display = 'block';
  30. }
  31. return; // Prevent adding a new container
  32. }
  33.  
  34. // Create a new main container to hold the output data
  35. mainContainer = document.createElement('div');
  36. mainContainer.id = 'questionDataContainer';
  37.  
  38. // Ensure the body is loaded before appending
  39. if (document.body) {
  40. document.body.appendChild(mainContainer);
  41. } else {
  42. window.addEventListener('DOMContentLoaded', () => {
  43. document.body.appendChild(mainContainer);
  44. });
  45. }
  46.  
  47. // Style for the main container and question divs
  48. const style = document.createElement('style');
  49. style.innerHTML = `
  50. @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&display=swap');
  51.  
  52. #questionDataContainer {
  53. position: fixed;
  54. top: 10px;
  55. right: 10px;
  56. width: 300px;
  57. height: 500px;
  58. overflow-y: auto;
  59. background-color: #1a1a1a;
  60. border: 2px solid #131313;
  61. padding: 10px;
  62. z-index: 9999;
  63. font-family: Poppins, sans-serif;
  64. font-size: 12px;
  65. border-radius: .8vw;
  66. color: white;
  67. transition: all .3s ease;
  68. }
  69.  
  70. #questionDataContainer::-webkit-scrollbar {
  71. width: .6vw;
  72. height: 12px;
  73. }
  74.  
  75. #questionDataContainer::-webkit-scrollbar-track {
  76. background: #141414;
  77. }
  78.  
  79. #questionDataContainer::-webkit-scrollbar-thumb {
  80. background-color: #24c39d;
  81. border-radius: 1vw;
  82. }
  83.  
  84. #questionDataContainer.minimized {
  85. width: 67px;
  86. height: 38px;
  87. overflow: hidden;
  88. border-radius: 10px;
  89. padding: 0;
  90. }
  91.  
  92. #minimizeButton {
  93. position: absolute;
  94. top: 5px;
  95. right: 5px;
  96. background-color: #333;
  97. color: white;
  98. border: none;
  99. border-radius: 5px;
  100. font-size: 12px;
  101. padding: 5px;
  102. cursor: pointer;
  103. font-family: Poppins, sans-serif;
  104. transition: all .3s ease;
  105. }
  106.  
  107. #minimizeButton:hover {
  108. background-color: #444;
  109. transition: all .3s ease;
  110. }
  111.  
  112. #questionDataContainer.minimized #khanHackHeader {
  113. opacity: 0;
  114. }
  115.  
  116. #khanHackHeader {
  117. text-align: center;
  118. font-family: Poppins, sans-serif;
  119. margin-bottom: 0px;
  120. font-size: 28px;
  121. color: #fff;
  122. }
  123.  
  124. #khanHackHeader span {
  125. color: #24c39d;
  126. }
  127.  
  128. .question-div {
  129. margin-bottom: 10px;
  130. padding: 10px;
  131. border-radius: 5px;
  132. color: black;
  133. overflow: hidden;
  134. }
  135.  
  136. .radio-div {
  137. background-color: #e0ffe0;
  138. }
  139.  
  140. .expression-div {
  141. background-color: #fff0f0;
  142. }
  143.  
  144. .dropdown-div {
  145. background-color: #f0f0ff;
  146. }
  147.  
  148. .orderer-div {
  149. background-color: #f5f5dc;
  150. }
  151.  
  152. .input-div {
  153. background-color: #ffefd5;
  154. }
  155.  
  156. .plotter-div {
  157. background-color: #ffe4b5;
  158. }
  159.  
  160. .no-support-div {
  161. background-color: #ffcccc;
  162. }
  163.  
  164. #answerToggle, #refreshButton {
  165. display: block;
  166. margin: 10px auto;
  167. padding: 5px;
  168. font-size: 14px;
  169. background-color: #333;
  170. color: white;
  171. border: 1px solid #444;
  172. cursor: pointer;
  173. border-radius: 5px;
  174. font-family: Poppins, sans-serif;
  175. font-weight: 400;
  176. transition: all .3s ease;
  177. }
  178.  
  179. #answerToggle:hover, #refreshButton:hover {
  180. background-color: #444;
  181. transition: all .3s ease;
  182. }
  183.  
  184. #answerContainer {
  185. display: block;
  186. }
  187.  
  188. #restoreButton {
  189. display: none;
  190. position: absolute;
  191. top: 5px;
  192. right: 5px;
  193. background-color: #333;
  194. color: white;
  195. border: none;
  196. border-radius: 5px;
  197. font-size: 12px;
  198. padding: 5px;
  199. cursor: pointer;
  200. font-family: Poppins, sans-serif;
  201. }
  202.  
  203. #restoreButton.minimized {
  204. display: block;
  205. }
  206.  
  207. img.img-ans {
  208. max-width: 100%;
  209. height: auto;
  210. border-radius: 5px;
  211. margin-top: 10px;
  212. }
  213. `;
  214. document.head.appendChild(style);
  215.  
  216. // Add header, buttons, and answer container
  217. const header = document.createElement('h1');
  218. header.id = 'khanHackHeader';
  219. header.innerHTML = 'Khan<span>Hack</span>';
  220.  
  221. const answerToggle = document.createElement('button');
  222. answerToggle.id = 'answerToggle';
  223. answerToggle.textContent = 'Hide Answers';
  224. let answersVisible = true;
  225.  
  226. const refreshButton = document.createElement('button');
  227. refreshButton.id = 'refreshButton';
  228. refreshButton.textContent = 'Reset Answer List';
  229.  
  230. const minimizeButton = document.createElement('button');
  231. minimizeButton.id = 'minimizeButton';
  232. minimizeButton.textContent = 'Minimize';
  233.  
  234. const restoreButton = document.createElement('button');
  235. restoreButton.id = 'restoreButton';
  236. restoreButton.textContent = 'Restore';
  237.  
  238. const answerContainer = document.createElement('div');
  239. answerContainer.id = 'answerContainer';
  240.  
  241. mainContainer.appendChild(minimizeButton);
  242. mainContainer.appendChild(header);
  243. mainContainer.appendChild(answerToggle);
  244. mainContainer.appendChild(refreshButton);
  245. mainContainer.appendChild(answerContainer);
  246. mainContainer.appendChild(restoreButton);
  247.  
  248. // Add functionality to hide/unhide the answers
  249. answerToggle.addEventListener('click', () => {
  250. answersVisible = !answersVisible;
  251. answerContainer.style.display = answersVisible ? 'block' : 'none';
  252. answerToggle.textContent = answersVisible ? 'Hide Answers' : 'Unhide Answers';
  253. });
  254.  
  255. // Add functionality to refresh the page
  256. refreshButton.addEventListener('click', () => {
  257. location.reload();
  258. });
  259.  
  260. // Add functionality to minimize the entire container
  261. minimizeButton.addEventListener('click', () => {
  262. mainContainer.classList.add('minimized');
  263. restoreButton.classList.add('minimized');
  264. minimizeButton.style.display = 'none';
  265. });
  266.  
  267. // Add functionality to restore the container
  268. restoreButton.addEventListener('click', () => {
  269. mainContainer.classList.remove('minimized');
  270. restoreButton.classList.remove('minimized');
  271. minimizeButton.style.display = 'block';
  272. });
  273.  
  274. // Utility function to convert decimal to simplified fraction
  275. function decimalToFraction(decimal) {
  276. if (decimal === 0) return "0";
  277.  
  278. let negative = decimal < 0;
  279. decimal = Math.abs(decimal); // Make it positive to simplify calculations
  280.  
  281. let whole = Math.trunc(decimal);
  282. let fraction = decimal - whole;
  283.  
  284. const gcd = (a, b) => b ? gcd(b, a % b) : a;
  285. const precision = 1000000;
  286. let numerator = Math.round(fraction * precision);
  287. let denominator = precision;
  288.  
  289. let commonDenominator = gcd(numerator, denominator);
  290. numerator = numerator / commonDenominator;
  291. denominator = denominator / commonDenominator;
  292.  
  293. let fractionString = numerator === 0 ? '' : `${Math.abs(numerator)}/${denominator}`;
  294. let result = whole !== 0 ? `${whole} ${fractionString}`.trim() : fractionString;
  295.  
  296. if (negative && result) {
  297. result = `-${result}`;
  298. }
  299.  
  300. if (whole === 0 && numerator !== 0) {
  301. result = `${negative ? '-' : ''}${numerator}/${denominator}`;
  302. }
  303.  
  304. return result;
  305. }
  306.  
  307. // Function to handle image replacement
  308. function replaceGraphieImage(content) {
  309. const imageRegex = /!\[.*?\]\(web\+graphie:\/\/cdn.kastatic.org\/ka-perseus-graphie\/([a-f0-9]+)\)/;
  310. const match = content.match(imageRegex);
  311. if (match && match[1]) {
  312. const imageId = match[1];
  313. return `<img src="https://cdn.kastatic.org/ka-perseus-graphie/${imageId}.svg" class="img-ans" />`;
  314. }
  315. return content;
  316. }
  317.  
  318. // Function to append content to the main container
  319. function appendToGUI(content, questionType) {
  320. try {
  321. const div = document.createElement('div');
  322. div.classList.add('question-div');
  323.  
  324. // Add specific class based on question type
  325. if (questionType === 'radio') {
  326. div.classList.add('radio-div');
  327. } else if (questionType === 'expression') {
  328. div.classList.add('expression-div');
  329. } else if (questionType === 'dropdown') {
  330. div.classList.add('dropdown-div');
  331. } else if (questionType === 'orderer') {
  332. div.classList.add('orderer-div');
  333. } else if (questionType === 'input') {
  334. div.classList.add('input-div');
  335. } else if (questionType === 'plotter') {
  336. div.classList.add('plotter-div');
  337. } else if (questionType === 'no-support') {
  338. div.classList.add('no-support-div');
  339. }
  340.  
  341. // Replace image markdown with <img> tag
  342. content = replaceGraphieImage(content);
  343.  
  344. div.innerHTML = content; // Use innerHTML to allow image rendering
  345. answerContainer.appendChild(div); // Append answers to the answer container
  346. } catch (error) {
  347. console.error("Failed to append content to GUI:", error);
  348. }
  349. }
  350.  
  351. // Function to clean up LaTeX-like expressions
  352. function cleanLatexExpression(expr) {
  353. if (typeof expr !== 'string') return expr; // Ensure it's a string before processing
  354. return expr.replace(/\\dfrac{(.+?)}{(.+?)}/g, '$1/$2') // Fractions
  355. .replace(/\\frac{(.+?)}{(.+?)}/g, '$1/$2') // Fractions
  356. .replace(/\\dfrac(\d+)(\d+)/g, '$1/$2') // Handle shorthand \\dfrac12 as 1/2
  357. .replace(/\\frac(\d+)(\d+)/g, '$1/$2') // Handle shorthand \\frac12 as 1/2
  358. .replace(/\\left\(/g, '(')
  359. .replace(/\\right\)/g, ')')
  360. .replace(/\\cdot/g, '*')
  361. .replace(/\\times/g, '*')
  362. .replace(/\\div/g, '/')
  363. .replace(/\\\\/g, '')
  364. .replace(/\\,/g, '')
  365. .replace(/\\sqrt{(.+?)}(.*?)/g, '√($1)')
  366. .replace(/\\sqrt/g, '√')
  367. .replace(/\\cos/g, 'cos') // Handle cosine function
  368. .replace(/\\sin/g, 'sin') // Handle sine function (in case you encounter it)
  369. .replace(/\\tan/g, 'tan') // Handle tangent function (in case you encounter it)
  370. .replace(/\\degree/g, '°') // Handle degree symbol
  371. .replace(/\\,/g, '')
  372. .replace(/\\\[/g, '[')
  373. .replace(/\\\]/g, ']')
  374. .replace(/\$/g, ''); // Remove dollar signs
  375. }
  376.  
  377. // Override fetch to capture /getAssessmentItem response
  378. window.fetch = function() {
  379. return originalFetch.apply(this, arguments).then(async (response) => {
  380. try {
  381. // Only handle responses that contain assessment items (questions/answers)
  382. if (response.url.includes("/getAssessmentItem")) {
  383. const clonedResponse = response.clone();
  384. const jsonData = await clonedResponse.json();
  385. const itemData = jsonData.data.assessmentItem.item.itemData;
  386. const questionData = JSON.parse(itemData).question;
  387.  
  388. // Log the full question data for debugging
  389. console.log('Full question data:', questionData);
  390.  
  391. // Initialize a variable to hold the combined answers for dropdowns per question
  392. const combinedAnswersPerQuestion = {};
  393. let numericInputAnswers = [];
  394.  
  395. // Iterate over each widget and handle the answer logic
  396. Object.keys(questionData.widgets).forEach(widgetKey => {
  397. const widget = questionData.widgets[widgetKey];
  398. let answer = "No answer available";
  399.  
  400. // Log the entire widget for debugging
  401. console.log('Widget data:', widget);
  402.  
  403. try {
  404. // Handle numeric-input type questions
  405. if (widget.type === "input-number" || widget.type === "numeric-input") {
  406. let answer;
  407.  
  408. // Check different possible paths for the answer, ensuring 0 is treated as a valid answer
  409. if (widget.options?.value !== undefined && widget.options?.value !== null) {
  410. answer = widget.options.value;
  411. } else if (widget.options?.answers?.[0]?.value !== undefined && widget.options?.answers?.[0]?.value !== null) {
  412. answer = widget.options.answers[0].value;
  413. }
  414.  
  415. // If the answer is found, push it to the numericInputAnswers array
  416. if (answer !== undefined && answer !== null) {
  417. numericInputAnswers.push(answer); // Add the answer to the array
  418. } else {
  419. console.error("Answer not found for widget:", widget);
  420. }
  421. }
  422.  
  423. // Handle graphing type questions
  424. else if (widget.type === "grapher" || widget.type === "interactive-graph") {
  425. if (widget.options?.correct?.coords && widget.options.correct.coords.length > 0) {
  426. const coords = widget.options.correct.coords.map(coord => `(${coord.join(", ")})`);
  427. appendToGUI(`Graphing Question: Correct Coordinates: ${coords.join(" and ")}`, 'plotter');
  428. console.log('Graphing answers (coordinates):', coords);
  429. } else {
  430. appendToGUI(`Graphing Question: No valid coordinates found`, 'plotter');
  431. console.log('Graphing Question: No valid coordinates found');
  432. }
  433. }
  434.  
  435. // Handle unsupported marker type questions
  436. else if (widget.type === "label-image") {
  437. appendToGUI("No hack support for this problem", 'no-support');
  438. }
  439.  
  440. // Handle intercept type questions
  441. else if (widget.type === "numeric-input" && widget.options?.answers) {
  442. const yIntercept = `(0, ${widget.options.answers[0].value})`;
  443. const xIntercept = `(${widget.options.answers[1].value}, 0)`;
  444. appendToGUI(`y-intercept: ${yIntercept}, x-intercept: ${xIntercept}`, 'input');
  445. }
  446.  
  447. // Handle dropdown type questions
  448. else if (widget.type === "dropdown") {
  449. if (widget.options?.choices) {
  450. answer = widget.options.choices.filter(c => c.correct).map(c => cleanLatexExpression(c.content));
  451. }
  452. const questionContent = questionData.content;
  453. if (!combinedAnswersPerQuestion[questionContent]) {
  454. combinedAnswersPerQuestion[questionContent] = [];
  455. }
  456. combinedAnswersPerQuestion[questionContent].push(...answer);
  457. }
  458.  
  459. // Handle expression type questions
  460. else if (widget.type === "expression") {
  461. if (widget.options?.answerForms && widget.options.answerForms.length > 1) {
  462. answer = widget.options.answerForms.filter(af => af.considered === "correct").map(af => cleanLatexExpression(af.value));
  463. appendToGUI(`Any of the following: ${JSON.stringify(answer, null, 2)}`, 'expression');
  464. } else {
  465. answer = widget.options.answerForms.filter(af => af.considered === "correct").map(af => cleanLatexExpression(af.value));
  466. appendToGUI(`Question Type: expression, Answer: ${JSON.stringify(answer, null, 2)}`, 'expression');
  467. }
  468. console.log('Expression answers:', answer);
  469. }
  470.  
  471. // Handle orderer type questions
  472. else if (widget.type === "orderer") {
  473. if (widget.options?.correctOptions) {
  474. const correctOrder = widget.options.correctOptions.map(option => option.content);
  475. appendToGUI(`Orderer Question: Correct Order: ${JSON.stringify(correctOrder, null, 2)}`, 'orderer');
  476. console.log('Orderer answers:', answer);
  477. }
  478. }
  479.  
  480. // Handle radio type questions
  481. else if (widget.type === "radio") {
  482. if (widget.options?.choices) {
  483. const correctChoices = widget.options.choices.filter(c => c.correct).map(c => cleanLatexExpression(c.content || "None of the above"));
  484. answer = correctChoices;
  485. answer = answer.map(choice => replaceGraphieImage(choice)); // Replace image markdown
  486. }
  487. appendToGUI(`Question Type: radio, Answer: ${answer.join(', ')}`, 'radio');
  488. console.log('Radio answers:', answer);
  489. }
  490.  
  491. // Handle plotter type questions
  492. else if (widget.type === "plotter") {
  493. const correctAnswers = widget.options?.correct || [];
  494. appendToGUI(`Data Plot Locations in Order: Answers: ${correctAnswers.join(", ")}`, 'plotter');
  495. console.log('Plotter correct answers:', correctAnswers);
  496. }
  497.  
  498. } catch (innerError) {
  499. console.error("Error processing widget:", widget, innerError);
  500. }
  501. });
  502.  
  503. if (numericInputAnswers.length > 0) {
  504. appendToGUI(`Numeric Input Question: Correct Answer(s): [${numericInputAnswers.join(', ')}]`, 'input');
  505. console.log(`Numeric Input Answers: [${numericInputAnswers.join(', ')}]`);
  506. }
  507.  
  508. // Display combined dropdown answers at once after all widgets are processed
  509. Object.keys(combinedAnswersPerQuestion).forEach(questionContent => {
  510. const finalCombinedAnswers = combinedAnswersPerQuestion[questionContent];
  511. appendToGUI(`Combined Answers: ${JSON.stringify(finalCombinedAnswers, null, 2)}`, 'dropdown');
  512. console.log('Dropdown combined answers:', finalCombinedAnswers);
  513. });
  514.  
  515. }
  516. return response;
  517. } catch (error) {
  518. console.error('Failed to fetch assessment data:', error);
  519. }
  520. }).catch((error) => {
  521. console.error("Network or fetch error:", error);
  522. });
  523. };
  524. })();
  525. })();

QingJ © 2025

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