Skip to content

Instantly share code, notes, and snippets.

@Destaq
Last active July 30, 2023 11:03
Show Gist options
  • Save Destaq/44dff878f0dae5ca906f42e766db5233 to your computer and use it in GitHub Desktop.
Save Destaq/44dff878f0dae5ca906f42e766db5233 to your computer and use it in GitHub Desktop.
Allows you to automatically view the answers to Save My Exams topic questions without a paid account, for all syllabi and question types. Developed to improve my TamperMonkey skills; use at your own responsibility.
// ==UserScript==
// @name Save My Exams Answers for All
// @namespace http://tampermonkey.net/
// @version 1.2.2
// @description Unlocks answers to all SME topic questions.
// @author Destaq
// @match https://www.savemyexams.co.uk/**/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=savemyexams.co.uk
// @grant none
// ==/UserScript==
// Gather all the scripts that are used to hold answer images.
function findRelevantScript(dataHash) {
let scripts = document.getElementsByTagName("script");
let match = null;
for (let i = 0; i < scripts.length; i++) {
if (scripts[i].innerHTML.startsWith(`$(function(){$('.question-part[data-partHash="${dataHash}`)) {
// NB: the matching length is 63 (or 64)
match = scripts[i];
break;
}
}
return match;
}
// Find the data-parthash of a button that has been clicked on.
function getDataPartHash(element) {
let dataPartHash = element.parentNode.parentNode.parentNode.getAttribute("data-parthash");
return dataPartHash;
}
// Replaces the VIP modal paywall with the answer image (usually for math), and does some minor restyling.
function displayUnlockedAnswer(answerURL) {
// It takes a moment to open the popup client-side.
setTimeout(function() {
let modal = document.querySelector(".modal-content");
// Reformat the modal to fit its new content.
let modalDialog = document.querySelector(".modal-dialog");
modalDialog.style.maxWidth = "80%";
// Update the HTML with the solution.
modal.outerHTML = `
<div class="modal-content">
<div class="modal-body">
<h3 class="modal-title ui-draggable-handle" style="margin-top: -8px; margin-bottom: 8px;">Answer</h3>
<div id="modal-questions-solution-content" class="resource-content solution-content">
<div class="block" data-type="html">
<p>
<img style="display:block;margin-left:auto;margin-right:auto" src="${answerURL}">
</p>
</div>
</div>
</div>
</div>
`
}, 500)
}
// Sometimes the answer is actual HTML text.
function updateModalHTML(newHTML) {
// It takes a moment to open the popup client-side.
setTimeout(function() {
let modal = document.querySelector(".modal-content");
// Reformat the modal to fit its new content.
let modalDialog = document.querySelector(".modal-dialog");
modalDialog.style.maxWidth = "80%";
// Update the HTML with the solution.
modal.innerHTML = `
<div class="modal-body">
${newHTML}
</div>
`
// Refit potentially oversized image.
document.querySelector(".modal-body").children[0].children[0].style = "max-width: 100%";
}, 500)
}
// Sets up the main unlocker. This has to be in a separate function, as SME uses SPA architecture.
function setup() {
// Collect the needed solution buttons.
let buttons = document.querySelectorAll("button[data-action='solution']");
// Add an event listener to all relevant buttons.
buttons.forEach(button => {
// Remove any existing event listeners from previous page navigation.
let new_button = button.cloneNode(true);
button.parentNode.replaceChild(new_button, button);
new_button.addEventListener("click", function () {
const matchingHash = getDataPartHash(new_button);
// Extract the relevant JS file (which contains an answer URL).
const matchingFile = findRelevantScript(matchingHash);
// For the solutions returned as HTML.
let HTMLre = /"body":"(.*)","type":"html"/gm;
HTMLre = /solution: \[(\{.*\})\]/gm;
try {
const potentialHTMLAnswer = HTMLre.exec(matchingFile.innerHTML)[1];
// console.log(potentialHTMLAnswer);
let allAnswers = potentialHTMLAnswer.split("},{");
allAnswers[0] += "}"
for (var i = 1; i < allAnswers.length - 1; i++) {
allAnswers[i] = "{" + allAnswers[i] + "}"
}
if (allAnswers.length > 1) {
allAnswers[allAnswers.length -1] = "{" + allAnswers[allAnswers.length -1];
}
let HTMLPart = ``;
// There are often multiple chunks.
allAnswers.forEach(answer => {
if (answer[answer.length - 1] === "}" && answer[answer.length - 2] === "}") {
answer = answer.slice(answer, answer.length - 1);
}
let answerPart = JSON.parse(answer).body;
HTMLPart += answerPart;
});
updateModalHTML(HTMLPart);
} catch (error) {
console.log(error);
// For the solutions returned as images.
const re = /https:\/\/\S*png/gm;
const highResAnswerURL = matchingFile.innerHTML.match(re).pop();
// Update the answer modal.
displayUnlockedAnswer(highResAnswerURL);
}
});
});
}
(function() {
'use strict';
setup()
var pushState = history.pushState;
history.pushState = function () {
pushState.apply(history, arguments);
setup()
}
})();
@Destaq
Copy link
Author

Destaq commented Jan 18, 2023

And then the paywall killer, so you don't have to resort to using Incognito:

// ==UserScript==
// @name         Save My Exams Paywall Killer
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Annihilates the SME Revision Notes paywall & indiscriminately destroys ad banners.
// @author       Destaq
// @match        https://www.savemyexams.co.uk/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=savemyexams.co.uk
// @grant        none
// ==/UserScript==

function killer() {
    // Remove ad spaces.
    const ads = document.querySelectorAll('[data-sme-fuse-zone-name]');
    for (const ad of ads) {
        ad.remove();
    }

    // Relies on the localStorage key 'SME.revision-note-views'.
    localStorage.clear()
}

(function() {
    'use strict';

    killer();

    // Watch for page navigation within the SPA.
    var pushState = history.pushState;
    history.pushState = function () {
        pushState.apply(history, arguments);
        killer();
    }
}
)();

To use this, click on + in the Tampermonkey dashboard. A new script editor should load up. Select everything, delete it, then replace it with the above. Click save. You should then be set.

@Destaq
Copy link
Author

Destaq commented Mar 20, 2023

Disclaimer: this tool is for educational purposes only (was wondering how far my DOM manipulation skills would get me!). Use at your own responsibility, and join SaveMyExams as a VIP member if you find yourself in need of actually using their paywalled resources.

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