Files
Timetable-V2/server/parser/index.js
2022-05-04 14:34:27 +02:00

252 lines
8.1 KiB
JavaScript

import Prisma from "@prisma/client";
import axios from "axios";
import { log, getLogPath } from "../logs.js";
import { getAuthtoken, getTimetables } from "./dsbmobile.js";
import { parseSubstitutionPlan } from "./untis.js";
import fs from "fs";
const prisma = new Prisma.PrismaClient();
const dsbFiles = ["Schüler_Monitor - subst_001", "Schüler Morgen - subst_001"];
export class Parser {
dsbUser;
dsbPassword;
constructor(dsbUser, dsbPassword, interval) {
this.dsbUser = dsbUser;
this.dsbPassword = dsbPassword;
setInterval(() => this.updatePlan(), interval);
this.updatePlan();
}
async updatePlan() {
const startedAt = new Date();
try {
const data = await this.fetchDSB();
if (!data) {
throw "DSB request failed!";
}
const plans = [];
for (const entry of data) {
const data = await this.fetchFile(entry.url);
const parsed = parseSubstitutionPlan(data);
plans.push(parsed);
}
const parseEvent = await prisma.parseEvent.create({
data: {
logFile: getLogPath(),
originalData: "",
duration: new Date() - startedAt,
succeeded: true,
},
});
for (const plan of plans) {
await this.insertSubstitutions(plan, parseEvent);
}
} catch (error) {
await prisma.parseEvent.create({
data: {
logFile: getLogPath(),
originalData: error.toString(),
duration: new Date() - startedAt,
succeeded: false,
},
});
log("Parser / Main", "Parse event failed: " + error);
}
}
async fetchDSB() {
try {
const token = await getAuthtoken(this.dsbUser, this.dsbPassword);
const response = await getTimetables(token);
const timetables = response.filter((e) => dsbFiles.includes(e.title));
return timetables;
} catch (error) {
log("Parser / DSB Mobile", "Error getting data: " + error);
}
return false;
}
async fetchFile(url) {
const result = await axios.request({
method: "GET",
url: url,
responseEncoding: "binary",
});
return result.data;
}
async parsePlan(html) {
return parseSubstitutionPlan(html);
}
async insertSubstitutions(parsedData, parseEvent) {
const { updatedAt, date, changes } = parsedData;
const classList = await prisma.class.findMany();
const knownSubstitutions = await prisma.substitution.findMany({
where: {
date: new Date(new Date(date).setUTCHours(0, 0, 0, 0)),
removed: false,
},
});
for (const change of changes) {
const classes = this.getSubstitutionClasses(classList, change.class);
if (classes.length == 0) classes.push(change.class || "unknown");
// Workaround no currect match possible for subsitutions of this
// type beacuse they don't have a class and a subject attribute
if (change.type == "Sondereins." && !change.subject) {
change.subject = change.notes;
}
const matchingSubstitutionId = knownSubstitutions.findIndex(
(substitution) => {
return substitution.date == new Date(date).setUTCHours(0, 0, 0, 0) &&
substitution.type == (change.type == "Entfall")
? "cancellation"
: "change" &&
substitution.lesson == change.lesson &&
classes.sort().join(",") ==
substitution.class.sort().join(",") &&
substitution.changedSubject == change.subject;
}
);
const matchingSubstitution = knownSubstitutions[matchingSubstitutionId];
if (!matchingSubstitution) {
const newSubstitution = await prisma.substitution.create({
data: {
class: classes,
date: new Date(date),
type: change.type == "Entfall" ? "cancellation" : "change",
lesson: parseInt(change.lesson),
changedTeacher: change.changedTeacher,
changedRoom: change.room || undefined,
changedSubject: change.subject,
notes: change.notes,
removed: false,
},
});
const substitutionChange = await prisma.substitutionChange.create({
data: {
substitutionId: newSubstitution.id,
type: "addition",
changes: {
class: classes,
type: change.type == "Entfall" ? "cancellation" : "change",
lesson: parseInt(change.lesson),
date: new Date(date),
notes: change.notes,
change: {
teacher: change.changedTeacher,
room: change.room || undefined,
subject: change.subject,
},
},
parseEventId: parseEvent.id,
},
});
log(
"Insert / DB",
`Created new substitution: S:${newSubstitution.id} C:${substitutionChange.id}`
);
} else {
const differences = this.findDifferences(matchingSubstitution, change);
if (Object.keys(differences).length > 0) {
const prismaOptions = {
where: {
id: matchingSubstitution.id,
},
data: {},
};
if (differences.teacher)
prismaOptions.data.changedTeacher = change.changedTeacher;
if (differences.room) prismaOptions.data.changedRoom = change.room;
if (differences.notes) prismaOptions.data.notes = change.notes;
await prisma.substitution.update(prismaOptions);
const substitutionChange = await prisma.substitutionChange.create({
data: {
substitutionId: matchingSubstitution.id,
type: "change",
changes: differences,
parseEventId: parseEvent.id,
},
});
log(
"Insert / DB",
`Found changed substitution: S:${matchingSubstitution.id} C:${substitutionChange.id}`
);
} else {
log(
"Insert / DB",
`Substitution unchanged: S:${matchingSubstitution.id}`
);
}
knownSubstitutions.splice(matchingSubstitutionId, 1);
}
}
for (const remainingSubstitution of knownSubstitutions) {
await prisma.substitution.update({
where: {
id: remainingSubstitution.id,
},
data: {
removed: true,
},
});
const substitutionChange = await prisma.substitutionChange.create({
data: {
substitutionId: remainingSubstitution.id,
type: "deletion",
changes: {
class: remainingSubstitution.class,
type: remainingSubstitution.type,
lesson: remainingSubstitution.lesson,
date: remainingSubstitution.date.getTime(),
notes: remainingSubstitution.notes,
change: {
teacher: remainingSubstitution.changedTeacher,
room: remainingSubstitution.changedRoom,
subject: remainingSubstitution.changedSubject,
},
},
parseEventId: parseEvent.id,
},
});
log(
"Insert / DB",
`Deleted removed substitution: S:${remainingSubstitution.id} C:${substitutionChange.id}`
);
}
}
getSubstitutionClasses(classList, classString) {
const matchingClasses = classList.filter((element) => {
const regex = new RegExp(element.regex);
return (classString || "").toLowerCase().match(regex);
});
return matchingClasses.map((e) => e.name);
}
findDifferences(currentSubstitution, newChange) {
const differences = {};
if (newChange.changedTeacher != currentSubstitution.changedTeacher) {
differences.teacher = {
before: currentSubstitution.changedTeacher,
after: newChange.changedTeacher,
};
}
if (newChange.room != currentSubstitution.changedRoom) {
differences.room = {
before: currentSubstitution.changedRoom,
after: newChange.room,
};
}
if (newChange.notes != currentSubstitution.notes) {
differences.notes = {
before: currentSubstitution.notes,
after: newChange.notes,
};
}
return differences;
}
}