// ==UserScript== // @name KhanHack // @namespace http://khan.dyntech.cc // @version 6.0 // @description Sloppy lazybones anti-homework production // @author DynTech // @match *://*.khanacademy.org/* // @oicon https://dyntech.cc/favicon?q=khan.dyntech.cc // @icon https://cdn.dyntech.cc/r/khanhack.png // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // This FREE script was extracted from a $7.99/month extension. You're welcome. (function() { const originalFetch = window.fetch; // Check for an existing container and restore it if minimized let mainContainer = document.getElementById('questionDataContainer'); if (mainContainer) { // If the container is minimized, restore it if (mainContainer.classList.contains('minimized')) { mainContainer.classList.remove('minimized'); const restoreButton = document.getElementById('restoreButton'); restoreButton.classList.remove('minimized'); const minimizeButton = document.getElementById('minimizeButton'); minimizeButton.style.display = 'block'; } return; // Prevent adding a new container } // Create a new main container to hold the output data mainContainer = document.createElement('div'); mainContainer.id = 'questionDataContainer'; // Ensure the body is loaded before appending if (document.body) { document.body.appendChild(mainContainer); } else { window.addEventListener('DOMContentLoaded', () => { document.body.appendChild(mainContainer); }); } // Style for the main container and question divs const style = document.createElement('style'); style.innerHTML = ` @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&display=swap'); #questionDataContainer { position: fixed; top: 10px; right: 10px; width: 300px; height: 500px; overflow-y: auto; background-color: #1a1a1a; border: 2px solid #131313; padding: 10px; z-index: 9999; font-family: Poppins, sans-serif; font-size: 12px; border-radius: .8vw; color: white; transition: all .3s ease; } #questionDataContainer::-webkit-scrollbar { width: .6vw; height: 12px; } #questionDataContainer::-webkit-scrollbar-track { background: #141414; } #questionDataContainer::-webkit-scrollbar-thumb { background-color: #24c39d; border-radius: 1vw; } #questionDataContainer.minimized { width: 67px; height: 38px; overflow: hidden; border-radius: 10px; padding: 0; } #minimizeButton { position: absolute; top: 5px; right: 5px; background-color: #333; color: white; border: none; border-radius: 5px; font-size: 12px; padding: 5px; cursor: pointer; font-family: Poppins, sans-serif; transition: all .3s ease; } #minimizeButton:hover { background-color: #444; transition: all .3s ease; } #questionDataContainer.minimized #khanHackHeader { opacity: 0; } #khanHackHeader { text-align: center; font-family: Poppins, sans-serif; margin-bottom: 0px; font-size: 28px; color: #fff; } #khanHackHeader span { color: #24c39d; } .question-div { margin-bottom: 10px; padding: 10px; border-radius: 5px; color: black; overflow: hidden; } .radio-div { background-color: #e0ffe0; } .expression-div { background-color: #fff0f0; } .dropdown-div { background-color: #f0f0ff; } .orderer-div { background-color: #f5f5dc; } .input-div { background-color: #ffefd5; } .plotter-div { background-color: #ffe4b5; } .no-support-div { background-color: #ffcccc; } #answerToggle, #refreshButton { display: block; margin: 10px auto; padding: 5px; font-size: 14px; background-color: #333; color: white; border: 1px solid #444; cursor: pointer; border-radius: 5px; font-family: Poppins, sans-serif; font-weight: 400; transition: all .3s ease; } #answerToggle:hover, #refreshButton:hover { background-color: #444; transition: all .3s ease; } #answerContainer { display: block; } #restoreButton { display: none; position: absolute; top: 5px; right: 5px; background-color: #333; color: white; border: none; border-radius: 5px; font-size: 12px; padding: 5px; cursor: pointer; font-family: Poppins, sans-serif; } #restoreButton.minimized { display: block; } img.img-ans { max-width: 100%; height: auto; border-radius: 5px; margin-top: 10px; } `; document.head.appendChild(style); // Add header, buttons, and answer container const header = document.createElement('h1'); header.id = 'khanHackHeader'; header.innerHTML = 'KhanHack'; const answerToggle = document.createElement('button'); answerToggle.id = 'answerToggle'; answerToggle.textContent = 'Hide Answers'; let answersVisible = true; const refreshButton = document.createElement('button'); refreshButton.id = 'refreshButton'; refreshButton.textContent = 'Reset Answer List'; const minimizeButton = document.createElement('button'); minimizeButton.id = 'minimizeButton'; minimizeButton.textContent = 'Minimize'; const restoreButton = document.createElement('button'); restoreButton.id = 'restoreButton'; restoreButton.textContent = 'Restore'; const answerContainer = document.createElement('div'); answerContainer.id = 'answerContainer'; mainContainer.appendChild(minimizeButton); mainContainer.appendChild(header); mainContainer.appendChild(answerToggle); mainContainer.appendChild(refreshButton); mainContainer.appendChild(answerContainer); mainContainer.appendChild(restoreButton); // Add functionality to hide/unhide the answers answerToggle.addEventListener('click', () => { answersVisible = !answersVisible; answerContainer.style.display = answersVisible ? 'block' : 'none'; answerToggle.textContent = answersVisible ? 'Hide Answers' : 'Unhide Answers'; }); // Add functionality to refresh the page refreshButton.addEventListener('click', () => { location.reload(); }); // Add functionality to minimize the entire container minimizeButton.addEventListener('click', () => { mainContainer.classList.add('minimized'); restoreButton.classList.add('minimized'); minimizeButton.style.display = 'none'; }); // Add functionality to restore the container restoreButton.addEventListener('click', () => { mainContainer.classList.remove('minimized'); restoreButton.classList.remove('minimized'); minimizeButton.style.display = 'block'; }); // Utility function to convert decimal to simplified fraction function decimalToFraction(decimal) { if (decimal === 0) return "0"; let negative = decimal < 0; decimal = Math.abs(decimal); // Make it positive to simplify calculations let whole = Math.trunc(decimal); let fraction = decimal - whole; const gcd = (a, b) => b ? gcd(b, a % b) : a; const precision = 1000000; let numerator = Math.round(fraction * precision); let denominator = precision; let commonDenominator = gcd(numerator, denominator); numerator = numerator / commonDenominator; denominator = denominator / commonDenominator; let fractionString = numerator === 0 ? '' : `${Math.abs(numerator)}/${denominator}`; let result = whole !== 0 ? `${whole} ${fractionString}`.trim() : fractionString; if (negative && result) { result = `-${result}`; } if (whole === 0 && numerator !== 0) { result = `${negative ? '-' : ''}${numerator}/${denominator}`; } return result; } // Function to handle image replacement function replaceGraphieImage(content) { const imageRegex = /!\[.*?\]\(web\+graphie:\/\/cdn.kastatic.org\/ka-perseus-graphie\/([a-f0-9]+)\)/; const match = content.match(imageRegex); if (match && match[1]) { const imageId = match[1]; return ``; } return content; } // Function to append content to the main container function appendToGUI(content, questionType) { try { const div = document.createElement('div'); div.classList.add('question-div'); // Add specific class based on question type if (questionType === 'radio') { div.classList.add('radio-div'); } else if (questionType === 'expression') { div.classList.add('expression-div'); } else if (questionType === 'dropdown') { div.classList.add('dropdown-div'); } else if (questionType === 'orderer') { div.classList.add('orderer-div'); } else if (questionType === 'input') { div.classList.add('input-div'); } else if (questionType === 'plotter') { div.classList.add('plotter-div'); } else if (questionType === 'no-support') { div.classList.add('no-support-div'); } // Replace image markdown with tag content = replaceGraphieImage(content); div.innerHTML = content; // Use innerHTML to allow image rendering answerContainer.appendChild(div); // Append answers to the answer container } catch (error) { console.error("Failed to append content to GUI:", error); } } // Function to clean up LaTeX-like expressions function cleanLatexExpression(expr) { if (typeof expr !== 'string') return expr; // Ensure it's a string before processing return expr.replace(/\\dfrac{(.+?)}{(.+?)}/g, '$1/$2') // Fractions .replace(/\\frac{(.+?)}{(.+?)}/g, '$1/$2') // Fractions .replace(/\\dfrac(\d+)(\d+)/g, '$1/$2') // Handle shorthand \\dfrac12 as 1/2 .replace(/\\frac(\d+)(\d+)/g, '$1/$2') // Handle shorthand \\frac12 as 1/2 .replace(/\\left\(/g, '(') .replace(/\\right\)/g, ')') .replace(/\\cdot/g, '*') .replace(/\\times/g, '*') .replace(/\\div/g, '/') .replace(/\\\\/g, '') .replace(/\\,/g, '') .replace(/\\sqrt{(.+?)}(.*?)/g, '√($1)') .replace(/\\sqrt/g, '√') .replace(/\\cos/g, 'cos') // Handle cosine function .replace(/\\sin/g, 'sin') // Handle sine function (in case you encounter it) .replace(/\\tan/g, 'tan') // Handle tangent function (in case you encounter it) .replace(/\\degree/g, '°') // Handle degree symbol .replace(/\\,/g, '') .replace(/\\\[/g, '[') .replace(/\\\]/g, ']') .replace(/\$/g, ''); // Remove dollar signs } // Override fetch to capture /getAssessmentItem response window.fetch = function() { return originalFetch.apply(this, arguments).then(async (response) => { try { // Only handle responses that contain assessment items (questions/answers) if (response.url.includes("/getAssessmentItem")) { const clonedResponse = response.clone(); const jsonData = await clonedResponse.json(); const itemData = jsonData.data.assessmentItem.item.itemData; const questionData = JSON.parse(itemData).question; // Log the full question data for debugging console.log('Full question data:', questionData); // Initialize a variable to hold the combined answers for dropdowns per question const combinedAnswersPerQuestion = {}; let numericInputAnswers = []; // Iterate over each widget and handle the answer logic Object.keys(questionData.widgets).forEach(widgetKey => { const widget = questionData.widgets[widgetKey]; let answer = "No answer available"; // Log the entire widget for debugging console.log('Widget data:', widget); try { // Handle numeric-input type questions if (widget.type === "input-number" || widget.type === "numeric-input") { let answer; // Check different possible paths for the answer, ensuring 0 is treated as a valid answer if (widget.options?.value !== undefined && widget.options?.value !== null) { answer = widget.options.value; } else if (widget.options?.answers?.[0]?.value !== undefined && widget.options?.answers?.[0]?.value !== null) { answer = widget.options.answers[0].value; } // If the answer is found, push it to the numericInputAnswers array if (answer !== undefined && answer !== null) { numericInputAnswers.push(answer); // Add the answer to the array } else { console.error("Answer not found for widget:", widget); } } // Handle graphing type questions else if (widget.type === "grapher" || widget.type === "interactive-graph") { if (widget.options?.correct?.coords && widget.options.correct.coords.length > 0) { const coords = widget.options.correct.coords.map(coord => `(${coord.join(", ")})`); appendToGUI(`Graphing Question: Correct Coordinates: ${coords.join(" and ")}`, 'plotter'); console.log('Graphing answers (coordinates):', coords); } else { appendToGUI(`Graphing Question: No valid coordinates found`, 'plotter'); console.log('Graphing Question: No valid coordinates found'); } } // Handle unsupported marker type questions else if (widget.type === "label-image") { appendToGUI("No hack support for this problem", 'no-support'); } // Handle intercept type questions else if (widget.type === "numeric-input" && widget.options?.answers) { const yIntercept = `(0, ${widget.options.answers[0].value})`; const xIntercept = `(${widget.options.answers[1].value}, 0)`; appendToGUI(`y-intercept: ${yIntercept}, x-intercept: ${xIntercept}`, 'input'); } // Handle dropdown type questions else if (widget.type === "dropdown") { if (widget.options?.choices) { answer = widget.options.choices.filter(c => c.correct).map(c => cleanLatexExpression(c.content)); } const questionContent = questionData.content; if (!combinedAnswersPerQuestion[questionContent]) { combinedAnswersPerQuestion[questionContent] = []; } combinedAnswersPerQuestion[questionContent].push(...answer); } // Handle expression type questions else if (widget.type === "expression") { if (widget.options?.answerForms && widget.options.answerForms.length > 1) { answer = widget.options.answerForms.filter(af => af.considered === "correct").map(af => cleanLatexExpression(af.value)); appendToGUI(`Any of the following: ${JSON.stringify(answer, null, 2)}`, 'expression'); } else { answer = widget.options.answerForms.filter(af => af.considered === "correct").map(af => cleanLatexExpression(af.value)); appendToGUI(`Question Type: expression, Answer: ${JSON.stringify(answer, null, 2)}`, 'expression'); } console.log('Expression answers:', answer); } // Handle orderer type questions else if (widget.type === "orderer") { if (widget.options?.correctOptions) { const correctOrder = widget.options.correctOptions.map(option => option.content); appendToGUI(`Orderer Question: Correct Order: ${JSON.stringify(correctOrder, null, 2)}`, 'orderer'); console.log('Orderer answers:', answer); } } // Handle radio type questions else if (widget.type === "radio") { if (widget.options?.choices) { const correctChoices = widget.options.choices.filter(c => c.correct).map(c => cleanLatexExpression(c.content || "None of the above")); answer = correctChoices; answer = answer.map(choice => replaceGraphieImage(choice)); // Replace image markdown } appendToGUI(`Question Type: radio, Answer: ${answer.join(', ')}`, 'radio'); console.log('Radio answers:', answer); } // Handle plotter type questions else if (widget.type === "plotter") { const correctAnswers = widget.options?.correct || []; appendToGUI(`Data Plot Locations in Order: Answers: ${correctAnswers.join(", ")}`, 'plotter'); console.log('Plotter correct answers:', correctAnswers); } } catch (innerError) { console.error("Error processing widget:", widget, innerError); } }); if (numericInputAnswers.length > 0) { appendToGUI(`Numeric Input Question: Correct Answer(s): [${numericInputAnswers.join(', ')}]`, 'input'); console.log(`Numeric Input Answers: [${numericInputAnswers.join(', ')}]`); } // Display combined dropdown answers at once after all widgets are processed Object.keys(combinedAnswersPerQuestion).forEach(questionContent => { const finalCombinedAnswers = combinedAnswersPerQuestion[questionContent]; appendToGUI(`Combined Answers: ${JSON.stringify(finalCombinedAnswers, null, 2)}`, 'dropdown'); console.log('Dropdown combined answers:', finalCombinedAnswers); }); } return response; } catch (error) { console.error('Failed to fetch assessment data:', error); } }).catch((error) => { console.error("Network or fetch error:", error); }); }; })(); })();