diff --git a/package.json b/package.json index 6b1b7451..bed1ea31 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "mathjs": "^14.4.0", "million": "^3.1.11", "motion": "^12.4.12", + "pdfjs-dist": "^5.4.530", "postcss": "^8.5.3", "react": "17", "react-best-gradient-color-picker": "3.0.11", diff --git a/src/plugins/built-in/assessmentsAverage/index.ts b/src/plugins/built-in/assessmentsAverage/index.ts index d3ef0051..79890d5d 100644 --- a/src/plugins/built-in/assessmentsAverage/index.ts +++ b/src/plugins/built-in/assessmentsAverage/index.ts @@ -7,6 +7,16 @@ import { import { type Plugin } from "@/plugins/core/types"; import stringToHTML from "@/seqta/utils/stringToHTML"; import { waitForElm } from "@/seqta/utils/waitForElm"; +import ReactFiber from "@/seqta/utils/ReactFiber.ts"; +import { getUserInfo } from "@/seqta/ui/AddBetterSEQTAElements.ts"; +import * as pdfjs from "pdfjs-dist"; +pdfjs.GlobalWorkerOptions.workerSrc = + "https://cdn.jsdelivr.net/npm/pdfjs-dist/build/pdf.worker.min.mjs"; + +// Storage +interface weightingsStorage { + weightings: Record; +} const settings = defineSettings({ lettergrade: booleanSetting({ @@ -23,7 +33,7 @@ class AssessmentsAveragePluginClass extends BasePlugin { const instance = new AssessmentsAveragePluginClass(); -const assessmentsAveragePlugin: Plugin = { +const assessmentsAveragePlugin: Plugin = { id: "assessments-average", name: "Assessment Averages", description: "Adds an average grade to the Assessments page", @@ -32,8 +42,13 @@ const assessmentsAveragePlugin: Plugin = { settings: instance.settings, run: async (api) => { + await api.storage.loaded; + + if (!api.storage.weightings) { + api.storage.weightings = {}; + } + api.seqta.onMount(".assessmentsWrapper", async () => { - // Wait for any assessment item to load first await waitForElm( "#main > .assessmentsWrapper .assessments [class*='AssessmentItem__AssessmentItem___']", true, @@ -41,6 +56,8 @@ const assessmentsAveragePlugin: Plugin = { 1000, ); + await parseAssessments(api); + // Helper function to find actual class names by their base pattern const getClassByPattern = ( element: Element | Document, @@ -201,4 +218,78 @@ const assessmentsAveragePlugin: Plugin = { }, }; +async function extractPDFText(url: string): Promise { + const loadingTask = pdfjs.getDocument(url); + const pdf = await loadingTask.promise; + + let text = ""; + + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const content = await page.getTextContent(); + text += content.items.map((item: any) => item.str).join(" ") + "\n"; + } + + return text; +} + +async function handleWeightings(mark: any, api: any) { + const assessmentID = mark.id; + const metaclassID = mark.metaclassID; + const userInfo = await getUserInfo(); + const userID = userInfo.id; + if (api.storage.weightings[assessmentID] != undefined) { + return; + } + + api.storage.weightings = { + ...api.storage.weightings, + [assessmentID]: "processing", + }; + + const filename = + "BetterSEQTA-" + String(Math.floor(Math.random() * 1e15)).padStart(15, "0"); + + await fetch(`${location.origin}/seqta/student/print/assessment`, { + method: "POST", + headers: { "Content-Type": "application/json; charset=utf-8" }, + body: JSON.stringify({ + fileName: filename, + id: assessmentID, + metaclass: metaclassID, + student: userID, + }), + }); + + const pdfUrl = `${location.origin}/seqta/student/report/get?file=${filename}`; + const text = await extractPDFText(pdfUrl); + + // Use regex to find the line "Assessment weight: X" + const match = text.match(/Assessment weight:\s*(\d+\.?\d*)/i); + const weight = match ? match[1] : "N/A"; + + // Save it to storage + api.storage.weightings = { + ...api.storage.weightings, + [assessmentID]: weight, + }; + + console.log(`Assessment ID ${assessmentID} weight:`, weight); + + console.log(text); +} + +async function parseAssessments(api: any) { + const state = await ReactFiber.find( + "[class*='AssessmentList__items___']", + ).getState(); + + const marks = state["marks"]; + if (!marks) return; + + for (const mark of marks) { + await handleWeightings(mark, api); + } +} + export default assessmentsAveragePlugin; diff --git a/src/seqta/ui/AddBetterSEQTAElements.ts b/src/seqta/ui/AddBetterSEQTAElements.ts index e7b5df62..a285fe0b 100644 --- a/src/seqta/ui/AddBetterSEQTAElements.ts +++ b/src/seqta/ui/AddBetterSEQTAElements.ts @@ -14,7 +14,7 @@ let cachedUserInfo: any = null; let LightDarkModeSnakeEggButton = 0; -async function getUserInfo() { +export async function getUserInfo() { if (cachedUserInfo) return cachedUserInfo; try {