epas-ripper/content.js
Gaspard Jankowiak 68a20e8126 convert to chrome
2026-06-04 22:13:36 +02:00

192 lines
5.3 KiB
JavaScript

"use strict";
const extensionApi = globalThis.browser ?? globalThis.chrome;
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 new Promise((resolve, reject) => {
extensionApi.runtime.sendMessage(
{ type: "COOKIE", url: "https://personal.uni-graz.at", key: "XSRF-TOKEN" },
(cookie) => {
const runtimeError = extensionApi.runtime.lastError;
if (runtimeError) {
reject(new Error(runtimeError.message))
return
}
if (cookie?.error != null) {
reject(new Error(cookie.error))
return
}
if (cookie?.value == null) {
reject(new Error("Failed to retrieve XSRF token cookie"))
return
}
resolve(cookie.value)
}
)
})
}
async function getApplicant(applicant, token) {
return fetch(`https://personal.uni-graz.at/api/erec/job-applications/${applicant.id}`,
{
credentials: "same-origin",
headers: { "X-XSRF-TOKEN": token }
})
.then((response) => {
if (response.ok) {
return response.json()
}
throw `Failed to get applicant ${applicant.id}`
})
.then(async (jsonData) => {
const files = jsonData.application_files ?? []
await Promise.all(files.map(async (afile) => {
const response = await fetch(
`https://personal.uni-graz.at/api/erec/download-file/${afile.file_id}`,
{
credentials: "same-origin",
headers: { "X-XSRF-TOKEN": token }
}
)
if (!response.ok) {
throw `Failed to download file ${afile.file_id} for applicant ${applicant.id}`
}
afile.blob = await response.blob()
}))
return jsonData
})
}
async function getApplicants() {
const aid = getApplicationId()
const token = await getXSRFToken()
return 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, 2);
return Promise.allSettled(sliced.map(async (applicant) => getApplicant(applicant, token)))
})
}
function sanitizeZipPathSegment(value, fallback = "unnamed") {
const sanitized = String(value ?? "")
.replace(/[\/\\:*?"<>|]/g, "_")
.trim()
return sanitized === "" ? fallback : sanitized
}
function downloadBlob(blob, fileName) {
const url = URL.createObjectURL(blob)
const link = document.createElement("a")
link.href = url
link.download = fileName
link.click()
setTimeout(() => URL.revokeObjectURL(url), 0)
}
function rip(event) {
const btn = event.target;
btn.textContent = "working ..."
btn.classList.add("loading")
btn.disabled = true;
getApplicants()
.then(async (applicants) => {
const zip = new JSZip()
applicants.forEach((result) => {
if (result.status !== "fulfilled") {
throw result.reason
}
const applicant = result.value
const dirName = `${applicant.last_name}_${applicant.first_name}`
// const dirName = `${sanitizeZipPathSegment(applicant.last_name)}_${sanitizeZipPathSegment(applicant.first_name)}`
const applicantDir = zip.folder(dirName)
const files = applicant.application_files ?? []
files.forEach((afile) => {
if (afile.blob.size > 2_000_000) return;
const ext = afile.file_name.split(".").slice(-1)
const filename = `${afile.field_name}.${ext}`
// const filename = sanitizeZipPathSegment(afile.file_name)
console.log(filename)
console.log(afile.blob)
applicantDir.file(filename, afile.blob)
})
})
const zipBlob = await zip.generateAsync({ type: "blob" })
downloadBlob(zipBlob, `applications_${getApplicationId()}.zip`)
btn.textContent = "done"
})
// .catch((error) => {
// console.error(error)
// btn.textContent = "failed"
// btn.disabled = false
// })
.finally(() => {
btn.classList.remove("loading")
})
}
// 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 });
console.log(JSZip.support)