This commit is contained in:
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
EMAIL=username@spsejecna.cz
|
||||
PASSWORD=mojesupertajneheslo
|
||||
SHAREPOINT_URL=https://spsejecnacz.sharepoint.com/:x:/s/nastenka/ESy19K245Y9BouR5ksciMvgBu3Pn_9EaT0fpP8R6MrkEmg
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,3 +10,5 @@ web/resources/_gen/
|
||||
web/assets/jsconfig.json
|
||||
web/hugo_stats.json
|
||||
web/.hugo_build.lock
|
||||
|
||||
.env
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"body-parser": "^2.2.0",
|
||||
"cheerio": "^1.1.2",
|
||||
"concurrently": "^9.2.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"exceljs": "^4.4.0",
|
||||
"express": "^5.1.0",
|
||||
"node-cron": "^4.2.1",
|
||||
@@ -946,7 +947,8 @@
|
||||
"version": "0.0.1452169",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1452169.tgz",
|
||||
"integrity": "sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==",
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
@@ -1003,6 +1005,18 @@
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"body-parser": "^2.2.0",
|
||||
"cheerio": "^1.1.2",
|
||||
"concurrently": "^9.2.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"exceljs": "^4.4.0",
|
||||
"express": "^5.1.0",
|
||||
"node-cron": "^4.2.1",
|
||||
|
||||
207
scrape/parse.js
207
scrape/parse.js
@@ -1,208 +1,5 @@
|
||||
import ExcelJS from "exceljs"
|
||||
import fs from "fs"
|
||||
import parseAbsence from "./utils/parseAbsence.js"
|
||||
import parseTeachers from "./utils/parseTeachers.js"
|
||||
import parseV1 from "./parse/v1.js";
|
||||
|
||||
export default async function parseThisShit(downloadedFilePath) {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.readFile(downloadedFilePath);
|
||||
const teacherMap = await parseTeachers();
|
||||
|
||||
const sheetNames = workbook.worksheets.map((sheet) => sheet.name);
|
||||
|
||||
const dateRegex = /^(pondělí|úterý|středa|čtvrtek|pátek|po|út|ut|st|čt|ct|pa|pá)\s+(\d{1,2})\.\s*(\d{1,2})\.\s*(20\d{2})/i;
|
||||
|
||||
// Get today's date for comparison
|
||||
function getCurrentDateObject() {
|
||||
const now = new Date();
|
||||
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
}
|
||||
|
||||
const today = getCurrentDateObject();
|
||||
|
||||
const upcomingSheets = sheetNames.filter((name) => {
|
||||
const match = name.match(dateRegex);
|
||||
if (!match) return false;
|
||||
|
||||
const day = Number.parseInt(match[2], 10);
|
||||
const month = Number.parseInt(match[3], 10) - 1; // JavaScript months are 0-indexed
|
||||
const year = Number.parseInt(match[4], 10);
|
||||
|
||||
const sheetDate = new Date(year, month, day);
|
||||
|
||||
return sheetDate >= today;
|
||||
})
|
||||
|
||||
const final = [];
|
||||
|
||||
let finalIndex = 0
|
||||
for (const key of upcomingSheets) {
|
||||
const currentSheet = workbook.getWorksheet(key);
|
||||
final.push({});
|
||||
|
||||
const regex = /[AEC][0-4][a-c]?\s*\/.*/s;
|
||||
const prefixRegex = /[AEC][0-4][a-c]?/;
|
||||
const classes = [];
|
||||
const matchingKeys = [];
|
||||
|
||||
currentSheet.eachRow((row) => {
|
||||
row.eachCell((cell) => {
|
||||
const cellAddress = cell.address;
|
||||
const value = cell.value;
|
||||
|
||||
if (value && typeof value === "string") {
|
||||
const testResult = regex.test(value);
|
||||
if (testResult && cellAddress.startsWith("A")) {
|
||||
const prefixMatch = value.match(prefixRegex);
|
||||
if (prefixMatch) {
|
||||
const prefix = prefixMatch[0];
|
||||
classes.push(prefix);
|
||||
}
|
||||
matchingKeys.push(cellAddress);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function letterToNumber(letter) {
|
||||
return letter.toLowerCase().charCodeAt(0) - "a".charCodeAt(0);
|
||||
}
|
||||
|
||||
// For each class
|
||||
let classI = 0;
|
||||
for (const matchingKey of matchingKeys) {
|
||||
const matchingCell = currentSheet.getCell(matchingKey);
|
||||
const rowNumber = matchingCell.row;
|
||||
const allKeys = [];
|
||||
|
||||
// Get all cells in the same row
|
||||
const row = currentSheet.getRow(rowNumber);
|
||||
row.eachCell((cell) => {
|
||||
if (cell.address !== matchingKey) {
|
||||
allKeys.push(cell.address);
|
||||
}
|
||||
})
|
||||
|
||||
let final2 = [];
|
||||
|
||||
for (const key of allKeys) {
|
||||
const cell = currentSheet.getCell(key);
|
||||
const parsedKey = letterToNumber(key.replace(/[0-9]/gi, ""));
|
||||
let d = true;
|
||||
|
||||
try {
|
||||
const regex = /^úklid\s+(?:\d+\s+)?[A-Za-z]{2}$/;
|
||||
const cellText = cell.text || "";
|
||||
if (regex.test(cellText.trim()) || cellText.trim().length == 0 || cell.fill?.fgColor === undefined) {
|
||||
d = false;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (d) {
|
||||
let text = cell.text;
|
||||
if (cell.fill?.fgColor?.argb == "FFFFFF00") {
|
||||
text += "\n(bude upřesněno)";
|
||||
}
|
||||
final2[parsedKey] = text || "";
|
||||
} else {
|
||||
final2[parsedKey] = null;
|
||||
}
|
||||
}
|
||||
|
||||
final2 = Array.from(final2, (item) => (item === undefined ? null : item));
|
||||
while (final2.length < 10) {
|
||||
final2.push(null);
|
||||
}
|
||||
|
||||
final[finalIndex][classes[classI]] = final2.slice(1, 11);
|
||||
|
||||
classI++;
|
||||
}
|
||||
|
||||
// ABSENCE
|
||||
final[finalIndex]["ABSENCE"] = [];
|
||||
let absenceKey = null;
|
||||
|
||||
currentSheet.eachRow((row) => {
|
||||
row.eachCell((cell) => {
|
||||
const value = (typeof cell.value === "string" ? cell.value : "").trim().toLowerCase();
|
||||
if (value === "absence") {
|
||||
absenceKey = cell.address;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (absenceKey) {
|
||||
const absenceCell = currentSheet.getCell(absenceKey);
|
||||
const rowNumber = absenceCell.row;
|
||||
const allAbsenceKeys = [];
|
||||
|
||||
// Get all cells in the same row as absence
|
||||
const row = currentSheet.getRow(rowNumber);
|
||||
row.eachCell((cell) => {
|
||||
if (cell.address !== absenceKey) {
|
||||
allAbsenceKeys.push(cell.address);
|
||||
}
|
||||
})
|
||||
|
||||
let i = 0;
|
||||
for (const absenceKeyCur of allAbsenceKeys) {
|
||||
if (i >= 10) {
|
||||
break; // stop once 10 items are added
|
||||
}
|
||||
i++;
|
||||
|
||||
const cell = currentSheet.getCell(absenceKeyCur);
|
||||
const value = (cell.value || "").toString().trim();
|
||||
if (value.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const data = parseAbsence(value, teacherMap);
|
||||
final[finalIndex]["ABSENCE"].push(...data);
|
||||
}
|
||||
}
|
||||
|
||||
finalIndex++;
|
||||
}
|
||||
|
||||
const currentDate = new Date();
|
||||
const formattedDate = currentDate.getHours().toString().padStart(2, "0") + ":" + currentDate.getMinutes().toString().padStart(2, "0");
|
||||
|
||||
const data = {
|
||||
schedule: final,
|
||||
props: upcomingSheets.map((str) => {
|
||||
const dateMatch = str.match(/(\d{1,2})\.\s*(\d{1,2})\.\s*(\d{4})/);
|
||||
|
||||
let date = null;
|
||||
|
||||
if (dateMatch) {
|
||||
const day = Number.parseInt(dateMatch[1], 10);
|
||||
const month = Number.parseInt(dateMatch[2], 10);
|
||||
const year = Number.parseInt(dateMatch[3], 10);
|
||||
|
||||
date = new Date(year, month - 1, day);
|
||||
}
|
||||
|
||||
const isPriprava = str
|
||||
.toLowerCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.includes("priprava");
|
||||
|
||||
return {
|
||||
date: date
|
||||
? `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`
|
||||
: null,
|
||||
priprava: isPriprava,
|
||||
};
|
||||
}),
|
||||
status: {
|
||||
lastUpdated: formattedDate,
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync("db/current.json", JSON.stringify(data, null, 2));
|
||||
await parseV1(downloadedFilePath)
|
||||
}
|
||||
|
||||
//parseThisShit("downloads/table.xlsx")
|
||||
|
||||
208
scrape/parse/v1.js
Normal file
208
scrape/parse/v1.js
Normal file
@@ -0,0 +1,208 @@
|
||||
import ExcelJS from "exceljs"
|
||||
import fs from "fs"
|
||||
import parseAbsence from "../utils/parseAbsence.js"
|
||||
import parseTeachers from "../utils/parseTeachers.js"
|
||||
|
||||
export default async function parseV1(downloadedFilePath) {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.readFile(downloadedFilePath);
|
||||
const teacherMap = await parseTeachers();
|
||||
|
||||
const sheetNames = workbook.worksheets.map((sheet) => sheet.name);
|
||||
|
||||
const dateRegex = /^(pondělí|úterý|středa|čtvrtek|pátek|po|út|ut|st|čt|ct|pa|pá)\s+(\d{1,2})\.\s*(\d{1,2})\.\s*(20\d{2})/i;
|
||||
|
||||
// Get today's date for comparison
|
||||
function getCurrentDateObject() {
|
||||
const now = new Date();
|
||||
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
}
|
||||
|
||||
const today = getCurrentDateObject();
|
||||
|
||||
const upcomingSheets = sheetNames.filter((name) => {
|
||||
const match = name.match(dateRegex);
|
||||
if (!match) return false;
|
||||
|
||||
const day = Number.parseInt(match[2], 10);
|
||||
const month = Number.parseInt(match[3], 10) - 1; // JavaScript months are 0-indexed
|
||||
const year = Number.parseInt(match[4], 10);
|
||||
|
||||
const sheetDate = new Date(year, month, day);
|
||||
|
||||
return sheetDate >= today;
|
||||
})
|
||||
|
||||
const final = [];
|
||||
|
||||
let finalIndex = 0
|
||||
for (const key of upcomingSheets) {
|
||||
const currentSheet = workbook.getWorksheet(key);
|
||||
final.push({});
|
||||
|
||||
const regex = /[AEC][0-4][a-c]?\s*\/.*/s;
|
||||
const prefixRegex = /[AEC][0-4][a-c]?/;
|
||||
const classes = [];
|
||||
const matchingKeys = [];
|
||||
|
||||
currentSheet.eachRow((row) => {
|
||||
row.eachCell((cell) => {
|
||||
const cellAddress = cell.address;
|
||||
const value = cell.value;
|
||||
|
||||
if (value && typeof value === "string") {
|
||||
const testResult = regex.test(value);
|
||||
if (testResult && cellAddress.startsWith("A")) {
|
||||
const prefixMatch = value.match(prefixRegex);
|
||||
if (prefixMatch) {
|
||||
const prefix = prefixMatch[0];
|
||||
classes.push(prefix);
|
||||
}
|
||||
matchingKeys.push(cellAddress);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function letterToNumber(letter) {
|
||||
return letter.toLowerCase().charCodeAt(0) - "a".charCodeAt(0);
|
||||
}
|
||||
|
||||
// For each class
|
||||
let classI = 0;
|
||||
for (const matchingKey of matchingKeys) {
|
||||
const matchingCell = currentSheet.getCell(matchingKey);
|
||||
const rowNumber = matchingCell.row;
|
||||
const allKeys = [];
|
||||
|
||||
// Get all cells in the same row
|
||||
const row = currentSheet.getRow(rowNumber);
|
||||
row.eachCell((cell) => {
|
||||
if (cell.address !== matchingKey) {
|
||||
allKeys.push(cell.address);
|
||||
}
|
||||
})
|
||||
|
||||
let final2 = [];
|
||||
|
||||
for (const key of allKeys) {
|
||||
const cell = currentSheet.getCell(key);
|
||||
const parsedKey = letterToNumber(key.replace(/[0-9]/gi, ""));
|
||||
let d = true;
|
||||
|
||||
try {
|
||||
const regex = /^úklid\s+(?:\d+\s+)?[A-Za-z]{2}$/;
|
||||
const cellText = cell.text || "";
|
||||
if (regex.test(cellText.trim()) || cellText.trim().length == 0 || cell.fill?.fgColor === undefined) {
|
||||
d = false;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (d) {
|
||||
let text = cell.text;
|
||||
if (cell.fill?.fgColor?.argb == "FFFFFF00") {
|
||||
text += "\n(bude upřesněno)";
|
||||
}
|
||||
final2[parsedKey] = text || "";
|
||||
} else {
|
||||
final2[parsedKey] = null;
|
||||
}
|
||||
}
|
||||
|
||||
final2 = Array.from(final2, (item) => (item === undefined ? null : item));
|
||||
while (final2.length < 10) {
|
||||
final2.push(null);
|
||||
}
|
||||
|
||||
final[finalIndex][classes[classI]] = final2.slice(1, 11);
|
||||
|
||||
classI++;
|
||||
}
|
||||
|
||||
// ABSENCE
|
||||
final[finalIndex]["ABSENCE"] = [];
|
||||
let absenceKey = null;
|
||||
|
||||
currentSheet.eachRow((row) => {
|
||||
row.eachCell((cell) => {
|
||||
const value = (typeof cell.value === "string" ? cell.value : "").trim().toLowerCase();
|
||||
if (value === "absence") {
|
||||
absenceKey = cell.address;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (absenceKey) {
|
||||
const absenceCell = currentSheet.getCell(absenceKey);
|
||||
const rowNumber = absenceCell.row;
|
||||
const allAbsenceKeys = [];
|
||||
|
||||
// Get all cells in the same row as absence
|
||||
const row = currentSheet.getRow(rowNumber);
|
||||
row.eachCell((cell) => {
|
||||
if (cell.address !== absenceKey) {
|
||||
allAbsenceKeys.push(cell.address);
|
||||
}
|
||||
})
|
||||
|
||||
let i = 0;
|
||||
for (const absenceKeyCur of allAbsenceKeys) {
|
||||
if (i >= 10) {
|
||||
break; // stop once 10 items are added
|
||||
}
|
||||
i++;
|
||||
|
||||
const cell = currentSheet.getCell(absenceKeyCur);
|
||||
const value = (cell.value || "").toString().trim();
|
||||
if (value.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const data = parseAbsence(value, teacherMap);
|
||||
final[finalIndex]["ABSENCE"].push(...data);
|
||||
}
|
||||
}
|
||||
|
||||
finalIndex++;
|
||||
}
|
||||
|
||||
const currentDate = new Date();
|
||||
const formattedDate = currentDate.getHours().toString().padStart(2, "0") + ":" + currentDate.getMinutes().toString().padStart(2, "0");
|
||||
|
||||
const data = {
|
||||
schedule: final,
|
||||
props: upcomingSheets.map((str) => {
|
||||
const dateMatch = str.match(/(\d{1,2})\.\s*(\d{1,2})\.\s*(\d{4})/);
|
||||
|
||||
let date = null;
|
||||
|
||||
if (dateMatch) {
|
||||
const day = Number.parseInt(dateMatch[1], 10);
|
||||
const month = Number.parseInt(dateMatch[2], 10);
|
||||
const year = Number.parseInt(dateMatch[3], 10);
|
||||
|
||||
date = new Date(year, month - 1, day);
|
||||
}
|
||||
|
||||
const isPriprava = str
|
||||
.toLowerCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.includes("priprava");
|
||||
|
||||
return {
|
||||
date: date
|
||||
? `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`
|
||||
: null,
|
||||
priprava: isPriprava,
|
||||
};
|
||||
}),
|
||||
status: {
|
||||
lastUpdated: formattedDate,
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync("db/v1.json", JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
//parseThisShit("downloads/table.xlsx")
|
||||
@@ -2,6 +2,7 @@ import puppeteer from 'puppeteer';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import parseThisShit from './parse.js';
|
||||
import 'dotenv/config';
|
||||
|
||||
const EMAIL = process.env.EMAIL;
|
||||
const PASSWORD = process.env.PASSWORD;
|
||||
|
||||
@@ -18,7 +18,7 @@ app.get('/', async (req, res) => {
|
||||
if (isBrowser) {
|
||||
res.sendFile(path.join(process.cwd(), "web", "public", "index.html"));
|
||||
} else {
|
||||
const dataStr = await fs.readFile(path.join(process.cwd(), "db", "current.json"), "utf8");
|
||||
const dataStr = await fs.readFile(path.join(process.cwd(), "db", "v1.json"), "utf8");
|
||||
const data = JSON.parse(dataStr);
|
||||
|
||||
data["status"]["currentUpdateSchedule"] = getCurrentInterval();
|
||||
@@ -28,7 +28,7 @@ app.get('/', async (req, res) => {
|
||||
});
|
||||
|
||||
app.get('/versioned/v1', async (_, res) => {
|
||||
const dataStr = await fs.readFile(path.join(process.cwd(), "db", "current.json"), "utf8");
|
||||
const dataStr = await fs.readFile(path.join(process.cwd(), "db", "v1.json"), "utf8");
|
||||
const data = JSON.parse(dataStr);
|
||||
|
||||
data["status"]["currentUpdateSchedule"] = getCurrentInterval();
|
||||
|
||||
Reference in New Issue
Block a user