From a8ae9c1ec31ff193ca353b02e2f78c16ff5207ab Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Sat, 20 Dec 2025 20:27:03 +0100 Subject: [PATCH] chore: Minor changes --- .env.example | 3 + .gitignore | 2 + package-lock.json | 16 +++- package.json | 1 + scrape/parse.js | 207 +------------------------------------------- scrape/parse/v1.js | 208 +++++++++++++++++++++++++++++++++++++++++++++ scrape/scraper.js | 1 + server.js | 4 +- 8 files changed, 234 insertions(+), 208 deletions(-) create mode 100644 .env.example create mode 100644 scrape/parse/v1.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6fab5b5 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +EMAIL=username@spsejecna.cz +PASSWORD=mojesupertajneheslo +SHAREPOINT_URL=https://spsejecnacz.sharepoint.com/:x:/s/nastenka/ESy19K245Y9BouR5ksciMvgBu3Pn_9EaT0fpP8R6MrkEmg diff --git a/.gitignore b/.gitignore index 4470b4a..588021e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ web/resources/_gen/ web/assets/jsconfig.json web/hugo_stats.json web/.hugo_build.lock + +.env diff --git a/package-lock.json b/package-lock.json index b2857b4..f8d9974 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 88c81bf..a4488a4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scrape/parse.js b/scrape/parse.js index e22d1da..82ac0e9 100644 --- a/scrape/parse.js +++ b/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") diff --git a/scrape/parse/v1.js b/scrape/parse/v1.js new file mode 100644 index 0000000..5c0bebd --- /dev/null +++ b/scrape/parse/v1.js @@ -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") diff --git a/scrape/scraper.js b/scrape/scraper.js index 0b6d584..39b8d3e 100644 --- a/scrape/scraper.js +++ b/scrape/scraper.js @@ -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; diff --git a/server.js b/server.js index 5b00764..1a65ab3 100644 --- a/server.js +++ b/server.js @@ -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();