From 45501c30f1b38c814de6c81323640c51dc89dc2c Mon Sep 17 00:00:00 2001 From: Gaspard Jankowiak Date: Wed, 3 Jun 2026 22:40:12 +0200 Subject: [PATCH] wip --- content.js | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 16 +++++++++ 2 files changed, 111 insertions(+) create mode 100644 content.js diff --git a/content.js b/content.js new file mode 100644 index 0000000..fd1a565 --- /dev/null +++ b/content.js @@ -0,0 +1,95 @@ +"use strict"; + +function getApplicationId() { + const match = window.location.hash.match(/^#\/job-procedures\/record\/([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})/) + if (match != null) { + return match[1] + } + throw "Unable to retrieve Application ID"; +} + +async function getXSRFToken() { + // we need to go through the background script to get the XSRF-TOKEN cookie + return browser.runtime.sendMessage( + { type: "COOKIE", url: "https://personal.uni-graz.at", key: "XSRF-TOKEN" } + ).then((cookie) => { + return cookie.value; + }) +} + +async function getApplicant(applicant, token) { + content.fetch(`https://personal.uni-graz.at/api/erec/job-applications/${applicant.id}`, + { + headers: { "X-XSRF-TOKEN": token } + }) + .then((response) => { + if (response.ok) { + return response.json() + } + throw `Failed to get applicant ${applicant.id}` + }) + .then((jsonData) => { + return jsonData + }) +} + +async function getApplicants() { + const aid = getApplicationId() + const token = await getXSRFToken() + content.fetch(`https://personal.uni-graz.at/api/erec/job-applications/procedure/${aid}`, + { + credentials: "same-origin", + headers: { "X-XSRF-TOKEN": token } + } + ) + .then((response) => { + if (response.ok) { + return response.json() + } + throw "Failed to get list of applications" + }) + .then((jsonData) => { + const sliced = jsonData.slice(0, 10); + return Promise.allSettled(sliced.map(async (applicant) => getApplicant(applicant, token))) + }) +} + +function rip(event) { + const btn = event.target; + btn.textContent = "working ..." + btn.classList.add("loading") + btn.disabled = true; + getApplicants().then((applicants) => { + btn.textContent = "done" + }) +} + +// injection of rip button +function install() { + const titleTag = document.querySelector("scrm-module-title") + + if (titleTag != null) { + const a = document.createElement("button") + a.textContent = "rip" + a.classList.add("ripper-btn") + a.addEventListener("click", rip, { + once: true + }) + titleTag.append(" (", a, ")") + } else { + console.log("could not install button") + } +} + +// we need the observer to restore the button after each page load +const contentObserver = new MutationObserver((mutations) => { + mutations.forEach(mu => { + for (const node of mu.removedNodes) { + if (node.nodeName == "APP-FULL-PAGE-SPINNER") { + install(); + } + } + }); +}) + +contentObserver.observe(document.querySelector("app-root"), { subtree: true, childList: true }); diff --git a/style.css b/style.css index e69de29..d77e0da 100644 --- a/style.css +++ b/style.css @@ -0,0 +1,16 @@ +/* .ripper-link { text-decoration: underline } */ + +/* from https://stackoverflow.com/questions/67605723/triple-dot-css-animation-on-a-loading-screen */ +.loading { + font-weight: bold; + display: inline-block; + font-family: monospace; + clip-path: inset(0 3ch 0 0); + animation: l 1s steps(4, jump-none) infinite; +} + +@keyframes l { + to { + clip-path: inset(0) + } +}