When doing puzzles, this will show you your stats
// ==UserScript==
// @name Lichess: Training: Stats for Current Run
// @version 1.1.0
// @author pedro-mass
// @copyright 2024, Pedro Mass (https://github.com/pedro-mass)
// @description When doing puzzles, this will show you your stats
// @grant none
// @icon https://www.google.com/s2/favicons?sz=64&domain=lichess.org
// @license GNU GPLv3
// @match https://lichess.org/training/*
// @match https://lichess.org/training
// @namespace http://tampermonkey.net/
// @run-at document-idle
// ==/UserScript==
const ids = {
stats: "pm-stats",
};
const selectors = {
results: ".result-empty",
stats: `#${ids.stats}`,
puzzleHolder: ".puzzle__session",
};
const constants = {
failure: "result-false",
success: "result-true",
};
waitForElement(document, selectors.results).then(() => {
run();
watchElement(document.querySelector(selectors.puzzleHolder), (changes) => {
if (wasTextChange(changes)) return;
run();
});
});
// ------------------
// helper functions
// ------------------
/**
* @param {MutationRecord[]} changes
*/
function wasTextChange(changes) {
if (!changes || changes.length === 0) return;
const firstChange = changes[0];
return (
firstChange.target.id === ids.stats && firstChange.type === "childList"
);
}
// src: https://stackoverflow.com/a/47406751/2911615
function waitForElement(root, selector) {
return new Promise((resolve, _reject) => {
new MutationObserver(check).observe(root, {
childList: true,
subtree: true,
});
function check(_changes, observer) {
let element = root.querySelector(selector);
if (element) {
observer.disconnect();
resolve(element);
}
}
});
}
function watchElement(root = document, onChange) {
new MutationObserver(onChange).observe(root, {
childList: true,
subtree: true,
});
}
function run() {
const statsElem = getStatsElem();
displayFailures(statsElem);
let showFailures = true;
statsElem.addEventListener("click", function flipDisplay() {
showFailures = !showFailures;
showFailures ? displayFailures(statsElem) : displaySuccesses(statsElem);
});
}
function getResults() {
return Array.from(document.querySelectorAll(selectors.results));
}
function getStatsElem() {
return document.querySelector(selectors.stats) ?? createStatsElem();
}
function createStatsElem() {
const statsElem = document.createElement("div");
statsElem.id = ids.stats;
document.querySelector(selectors.puzzleHolder).appendChild(statsElem);
return statsElem;
}
function getStats() {
const results = getResults();
const failures = results.filter((x) =>
Array.from(x.classList).includes(constants.failure)
);
const successes = results.filter((x) =>
Array.from(x.classList).includes(constants.success)
);
return {
total: results.length,
failures: failures.length,
successes: successes.length,
};
}
function displayFailures(elem) {
const { total, failures } = getStats();
elem.textContent = `${failures} / ${total} failures`;
}
function displaySuccesses(elem) {
const { total, successes } = getStats();
elem.textContent = `${successes} / ${total} successes`;
}