Skip to content

Instantly share code, notes, and snippets.

@kms0219kms
Last active March 25, 2025 18:02
Show Gist options
  • Save kms0219kms/07a44a7035ed89d47fcf9d24a621dbc8 to your computer and use it in GitHub Desktop.
Save kms0219kms/07a44a7035ed89d47fcf9d24a621dbc8 to your computer and use it in GitHub Desktop.
// ==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();
});
}
@kms0219kms
Copy link
Author

Update 2025-03-26: Update the code to use new SparxMaths domain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment