mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 09:11:06 +00:00
210 lines
5.6 KiB
TypeScript
210 lines
5.6 KiB
TypeScript
import { getUserInfo } from "@/seqta/ui/AddBetterSEQTAElements";
|
|
|
|
export interface ArchivedNotification {
|
|
notificationID: number;
|
|
type: string;
|
|
timestamp: string;
|
|
title: string;
|
|
subtitle: string;
|
|
messageID?: number;
|
|
assessmentID?: number;
|
|
programmeID?: number;
|
|
metaclassID?: number;
|
|
subjectCode?: string;
|
|
firstSavedAt: string;
|
|
lastSeenAt: string;
|
|
raw: RawNotification;
|
|
}
|
|
|
|
export type ArchiveMap = Record<string, ArchivedNotification>;
|
|
|
|
export type ArchivesByUser = Record<string, ArchiveMap>;
|
|
|
|
type RawNotification = Record<string, unknown>;
|
|
|
|
export async function resolveNotificationUserKey(): Promise<string | null> {
|
|
try {
|
|
const info = await getUserInfo();
|
|
const id = info?.id ?? info?.personUUID ?? info?.username;
|
|
if (id == null || id === "") return null;
|
|
const label =
|
|
info?.displayName ?? info?.name ?? info?.username ?? String(id);
|
|
return `${location.hostname}:${id}:${String(label).slice(0, 64)}`;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function fetchAllNotifications(): Promise<RawNotification[]> {
|
|
const res = await fetch(`${location.origin}/seqta/student/heartbeat?`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
credentials: "include",
|
|
body: JSON.stringify({
|
|
timestamp: "1970-01-01 00:00:00.0",
|
|
hash: "#?page=/notifications",
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) return [];
|
|
|
|
const json = (await res.json()) as {
|
|
notifications?: RawNotification[];
|
|
payload?: { notifications?: RawNotification[] };
|
|
};
|
|
|
|
const list = json.notifications ?? json.payload?.notifications;
|
|
return Array.isArray(list) ? list : [];
|
|
}
|
|
|
|
function readString(value: unknown): string {
|
|
if (value == null) return "";
|
|
return String(value).trim();
|
|
}
|
|
|
|
export function normalizeArchivedNotification(
|
|
raw: RawNotification,
|
|
now = new Date().toISOString(),
|
|
): ArchivedNotification | null {
|
|
const notificationID = Number(raw.notificationID);
|
|
if (!notificationID || Number.isNaN(notificationID)) return null;
|
|
|
|
const type = readString(raw.type) || "unknown";
|
|
const timestamp = readString(raw.timestamp) || now;
|
|
|
|
if (type === "message" && raw.message && typeof raw.message === "object") {
|
|
const message = raw.message as Record<string, unknown>;
|
|
return {
|
|
notificationID,
|
|
type,
|
|
timestamp,
|
|
title: readString(message.title) || "Message",
|
|
subtitle: readString(message.subtitle),
|
|
messageID: Number(message.messageID) || undefined,
|
|
firstSavedAt: now,
|
|
lastSeenAt: now,
|
|
raw: { ...raw },
|
|
};
|
|
}
|
|
|
|
if (
|
|
type === "coneqtassessments" &&
|
|
raw.coneqtAssessments &&
|
|
typeof raw.coneqtAssessments === "object"
|
|
) {
|
|
const assessment = raw.coneqtAssessments as Record<string, unknown>;
|
|
return {
|
|
notificationID,
|
|
type,
|
|
timestamp,
|
|
title: readString(assessment.title) || "Assessment",
|
|
subtitle: readString(assessment.subtitle) || readString(assessment.subjectCode),
|
|
assessmentID: Number(assessment.assessmentID) || undefined,
|
|
programmeID: Number(assessment.programmeID) || undefined,
|
|
metaclassID: Number(assessment.metaclassID) || undefined,
|
|
subjectCode: readString(assessment.subjectCode) || undefined,
|
|
firstSavedAt: now,
|
|
lastSeenAt: now,
|
|
raw: { ...raw },
|
|
};
|
|
}
|
|
|
|
return {
|
|
notificationID,
|
|
type,
|
|
timestamp,
|
|
title: readString(raw.title) || "Notification",
|
|
subtitle: readString(raw.subtitle),
|
|
firstSavedAt: now,
|
|
lastSeenAt: now,
|
|
raw: { ...raw },
|
|
};
|
|
}
|
|
|
|
export function mergeNotificationsIntoArchive(
|
|
existing: ArchiveMap,
|
|
notifications: RawNotification[],
|
|
): ArchiveMap {
|
|
const now = new Date().toISOString();
|
|
const merged: ArchiveMap = { ...existing };
|
|
|
|
for (const raw of notifications) {
|
|
const normalized = normalizeArchivedNotification(raw, now);
|
|
if (!normalized) continue;
|
|
|
|
const key = String(normalized.notificationID);
|
|
const prev = merged[key];
|
|
if (prev) {
|
|
merged[key] = {
|
|
...prev,
|
|
...normalized,
|
|
timestamp: normalized.timestamp || prev.timestamp,
|
|
firstSavedAt: prev.firstSavedAt,
|
|
lastSeenAt: now,
|
|
raw: { ...prev.raw, ...raw },
|
|
};
|
|
} else {
|
|
merged[key] = normalized;
|
|
}
|
|
}
|
|
|
|
return merged;
|
|
}
|
|
|
|
export function listArchivedNotifications(archive: ArchiveMap): ArchivedNotification[] {
|
|
return Object.values(archive).sort(
|
|
(a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
|
|
);
|
|
}
|
|
|
|
export function archivedToApiNotification(
|
|
item: ArchivedNotification,
|
|
): RawNotification {
|
|
if (item.raw && typeof item.raw === "object") {
|
|
return {
|
|
...item.raw,
|
|
notificationID: item.notificationID,
|
|
type: item.type,
|
|
timestamp: item.timestamp,
|
|
};
|
|
}
|
|
|
|
if (item.type === "message") {
|
|
return {
|
|
notificationID: item.notificationID,
|
|
type: "message",
|
|
timestamp: item.timestamp,
|
|
message: {
|
|
title: item.title,
|
|
subtitle: item.subtitle,
|
|
messageID: item.messageID,
|
|
},
|
|
};
|
|
}
|
|
|
|
if (item.type === "coneqtassessments") {
|
|
return {
|
|
notificationID: item.notificationID,
|
|
type: "coneqtassessments",
|
|
timestamp: item.timestamp,
|
|
coneqtAssessments: {
|
|
title: item.title,
|
|
subtitle: item.subtitle,
|
|
assessmentID: item.assessmentID,
|
|
programmeID: item.programmeID,
|
|
metaclassID: item.metaclassID,
|
|
subjectCode: item.subjectCode,
|
|
term: "",
|
|
},
|
|
};
|
|
}
|
|
|
|
return {
|
|
notificationID: item.notificationID,
|
|
type: item.type,
|
|
timestamp: item.timestamp,
|
|
title: item.title,
|
|
subtitle: item.subtitle,
|
|
};
|
|
}
|