feat: supporting improved assessments and improved parsing

This commit is contained in:
SethBurkart123
2025-05-05 17:58:40 +10:00
parent ec42f1bb27
commit 771169348f
3 changed files with 94 additions and 8 deletions
@@ -1,4 +1,6 @@
import type { Job, IndexItem } from "../types"; import type { Job, IndexItem } from "../types";
import { htmlToPlainText } from "../utils";
import { fetchMessageContent } from "./messages";
/* ------------- Notification types ------------- */ /* ------------- Notification types ------------- */
interface MessageNotification { interface MessageNotification {
@@ -44,6 +46,55 @@ const fetchNotifications = async () => {
return (json.notifications ?? []) as Notification[]; return (json.notifications ?? []) as Notification[];
}; };
const fetchAssessmentName = async (
assessmentId: number,
metaclassId: number,
programmeId: number
): Promise<string> => {
const searchAssessment = (data: any): string | null => {
// Search syllabus
for (const item of data.syllabus || []) {
const found = (item.assessments || []).find((a: any) => a.id === assessmentId);
if (found) return found.title;
}
// Search pending
const foundPending = (data.pending || []).find((a: any) => a.id === assessmentId);
if (foundPending) return foundPending.title;
// Search tasks
const foundTask = (data.tasks || []).find((a: any) => a.id === assessmentId);
if (foundTask) return foundTask.title;
return null;
};
const fetchAssessments = async (endpoint: string) => {
const res = await fetch(`${location.origin}${endpoint}`, {
method: "POST",
credentials: "include",
body: JSON.stringify({
metaclass: metaclassId,
programme: programmeId,
}),
});
const json = await res.json();
return json.payload;
};
// Try from /past
let payload = await fetchAssessments("/seqta/student/assessment/list/past");
let title = searchAssessment(payload);
if (title) return title;
// Try from /upcoming if not found in /past
const upcomingPayload = await fetchAssessments("/seqta/student/assessment/list/upcoming");
const foundUpcoming = (upcomingPayload || []).find((a: any) => a.id === assessmentId);
if (foundUpcoming) return foundUpcoming.title;
throw new Error(`Assessment with ID ${assessmentId} not found in past or upcoming.`);
};
/* ------------- Job ------------- */ /* ------------- Job ------------- */
export const assessmentsJob: Job = { export const assessmentsJob: Job = {
id: "assessments", id: "assessments",
@@ -79,11 +130,13 @@ export const assessmentsJob: Job = {
if (notif.type === "coneqtassessments") { if (notif.type === "coneqtassessments") {
const a = notif.coneqtAssessments; const a = notif.coneqtAssessments;
const content = await fetchAssessmentName(a.assessmentID, a.metaclassID, a.programmeID);
items.push({ items.push({
id, id,
text: a.title, text: a.title,
category: "assessments", category: "assessments",
content: a.subtitle, content: content,
dateAdded: new Date(notif.timestamp).getTime(), dateAdded: new Date(notif.timestamp).getTime(),
metadata: { metadata: {
assessmentId: a.assessmentID, assessmentId: a.assessmentID,
@@ -96,13 +149,15 @@ export const assessmentsJob: Job = {
actionId: "assessment", actionId: "assessment",
renderComponentId: "assessment", renderComponentId: "assessment",
}); });
} else { } else if (notif.type === "message") {
const content = await fetchMessageContent(notif.message.messageID);
await ctx.addItem( await ctx.addItem(
{ {
id, id,
text: notif.message.title, text: notif.message.title,
category: "messages", category: "messages",
content: `From: ${notif.message.subtitle}`, content: `${htmlToPlainText(content.payload.contents)}\nFrom: ${notif.message.subtitle}`,
dateAdded: new Date(notif.timestamp).getTime(), dateAdded: new Date(notif.timestamp).getTime(),
metadata: { metadata: {
messageId: notif.message.messageID, messageId: notif.message.messageID,
@@ -125,7 +180,7 @@ export const assessmentsJob: Job = {
); );
await ctx.setProgress({ lastTs: latest }); await ctx.setProgress({ lastTs: latest });
} }
return items; return items;
}, },
@@ -1,6 +1,5 @@
import type { Job, IndexItem } from "../types"; import type { Job, IndexItem } from "../types";
import { htmlToPlainText } from "../utils";
const stripHtmlTags = (html: string) => html.replace(/<[^>]*>/g, "");
const fetchMessages = async (offset = 0, limit = 100) => { const fetchMessages = async (offset = 0, limit = 100) => {
const res = await fetch(`${location.origin}/seqta/student/load/message`, { const res = await fetch(`${location.origin}/seqta/student/load/message`, {
@@ -24,7 +23,7 @@ const fetchMessages = async (offset = 0, limit = 100) => {
}>; }>;
}; };
const fetchMessageContent = async (id: number) => { export const fetchMessageContent = async (id: number) => {
const res = await fetch(`${location.origin}/seqta/student/load/message`, { const res = await fetch(`${location.origin}/seqta/student/load/message`, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
@@ -96,7 +95,7 @@ export const messagesJob: Job = {
id, id,
text: msg.subject, text: msg.subject,
category: "messages", category: "messages",
content: `From: ${msg.sender}\n\n${stripHtmlTags(full.payload.contents)}`, content: `${htmlToPlainText(full.payload.contents)}\nFrom: ${msg.sender}`,
dateAdded: new Date(msg.date).getTime(), dateAdded: new Date(msg.date).getTime(),
metadata: { metadata: {
messageId: msg.id, messageId: msg.id,
@@ -0,0 +1,32 @@
export function htmlToPlainText(rawHtml: string): string {
const parser = new DOMParser();
const doc = parser.parseFromString(rawHtml, 'text/html');
const { body } = doc;
body.querySelectorAll('script,style,template,noscript,meta,link').forEach(el => el.remove());
body.querySelectorAll('.forward').forEach(el => {
let n: ChildNode | null = el;
while (n) {
const next = n.nextSibling as ChildNode | null;
n.remove();
n = next;
}
});
let text = body.innerText || '';
text = text
.replace(/\u00A0/g, ' ')
.replace(/[ \t]{2,}/g, ' ')
.replace(/\r\n|\r/g, '\n')
.replace(/\n{3,}/g, '\n\n')
.replace(/^[.\w#][^{]{0,100}\{[^}]*\}$/gm, '')
.split('\n')
.map(line => line.trimEnd())
.filter(line => line.trim().length > 0 || line === '')
.join('\n')
.trim();
return text;
}