Last active
March 25, 2025 18:02
-
-
Save kms0219kms/07a44a7035ed89d47fcf9d24a621dbc8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name SparxMaths Questions Download | |
// @namespace https://maths.sparx-learning.com/ | |
// @version 2025-03-26 | |
// @description Download every questions into screenshots. | |
// @author Minsu Kim <[email protected]> | |
// @match https://www.sparxmaths.uk/student/* | |
// @match https://maths.sparx-learning.com/student/* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=maths.sparx-learning.com | |
// @grant none | |
// ==/UserScript== | |
(async () => { | |
"use strict"; | |
let oldUrl = window.location.href; | |
await requestInstallModernScreenshot(); | |
if (oldUrl.includes("/student/package") && !oldUrl.includes("summary")) { | |
console.log("[DOWNLOADER] Initalised. Creating the Download button..."); | |
await createButton(); | |
} | |
const routerObserver = new MutationObserver((mutations) => { | |
for (const mutation of mutations) { | |
if (mutation.type === 'childList') { | |
const newUrl = window.location.href; | |
if (newUrl !== oldUrl) { | |
oldUrl = newUrl; | |
console.log('Route changed to:', newUrl); | |
if (newUrl.includes("/student/package") && !newUrl.includes("summary")) { | |
console.log("[DOWNLOADER] Route change detected. Creating the Download button..."); | |
createButton(); | |
} else { | |
if (document.querySelector("#_Ayaan_DownloadBtn")) { | |
document.querySelector("#_Ayaan_DownloadBtn").remove(); | |
} | |
} | |
} | |
} | |
} | |
}); | |
routerObserver.observe(document.body, { | |
childList: true, | |
subtree: true | |
}); | |
setInterval(() => { | |
const downloadBtn = document.querySelectorAll('#_Ayaan_DownloadBtn'); | |
if (downloadBtn.length > 1) { | |
downloadBtn.forEach((x, i) => { | |
if (i !== 0) { | |
x.remove(); | |
} | |
}) | |
} | |
}); | |
})(); | |
// From: https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists | |
function waitForElm(selector) { | |
return new Promise((resolve) => { | |
if (document.querySelector(selector)) { | |
return resolve(document.querySelector(selector)); | |
} | |
const observer = new MutationObserver((mutations) => { | |
if (document.querySelector(selector)) { | |
observer.disconnect(); | |
resolve(document.querySelector(selector)); | |
} | |
}); | |
// If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336 | |
observer.observe(document.body, { | |
childList: true, | |
subtree: true, | |
}); | |
}); | |
} | |
// From: https://stackoverflow.com/questions/4793604/how-to-insert-an-element-after-another-element-in-javascript-without-using-a-lib | |
function insertAfter(referenceNode, newNode) { | |
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); | |
} | |
function requestInstallModernScreenshot() { | |
return new Promise((resolve) => { | |
if (window.modernScreenshot) { | |
return resolve(window.modernScreenshot); | |
} | |
console.log("[DOWNLOADER] Requesting to JSDelivr to install modern-screenshot..."); | |
const script = document.createElement("script"); | |
script.src = "https://cdn.jsdelivr.net/npm/modern-screenshot"; | |
document.getElementsByTagName("head")[0].appendChild(script); | |
script.onload = () => { | |
resolve(window.modernScreenshot); | |
}; | |
}); | |
} | |
async function createButton() { | |
if (document.querySelector('#_Ayaan_DownloadBtn')) { | |
return document.querySelector('#_Ayaan_DownloadBtn'); | |
} | |
await waitForElm('div[class*="_QuestionWrapper_"]'); | |
console.log("[DOWNLOADER] Question element is ready."); | |
const studentNameElm = document.querySelector('div[class*="_StudentName_"]'); | |
const button = document.createElement("a"); | |
const buttonStyle = Array.from(studentNameElm.previousSibling.classList); | |
button.id = "_Ayaan_DownloadBtn"; | |
button.href = "#"; | |
button.textContent = "Download"; | |
button.addEventListener("click", () => { | |
console.log("[DOWNLOADER] Download button has clicked."); | |
screenshot(); | |
}); | |
buttonStyle.forEach((c) => { | |
button.classList.add(c); | |
}); | |
insertAfter(studentNameElm, button); | |
console.log("[DOWNLOADER] Download button is ready."); | |
return document.querySelector('#_Ayaan_DownloadBtn'); | |
} | |
function screenshot() { | |
const currentQuestion = document.querySelector('a[class*="_Selected_"]').textContent; | |
const questionContainerElm = document.querySelector('div[class*="_QuestionContainer_"]'); | |
const questionElm = document.querySelector('div[class*="_QuestionWrapper_"]'); | |
console.log(`[DOWNLOADER] Downloading this question (${currentQuestion})...`); | |
questionContainerElm.style = 'max-width: 960px;'; | |
window.modernScreenshot | |
.domToPng(questionElm, { | |
debug: true, | |
backgroundColor: 'white', | |
progress: (current, total) => { | |
console.log(`[DOWNLOADER] Progress: ${((current / total) * 100) | 0}% (${current}/${total})`); | |
}, | |
}) | |
.then((dataUrl) => { | |
questionContainerElm.style = ''; | |
const link = document.createElement("a"); | |
link.download = currentQuestion + ".png"; | |
link.href = dataUrl; | |
link.click(); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Update 2025-03-26: Update the code to use new SparxMaths domain