Spaces:
Running
Running
| const express = require('express'); | |
| const { chromium } = require('playwright'); | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| // ========================= | |
| // CONFIG | |
| // ========================= | |
| const DATA_FILE = 'latest.json'; | |
| const DOWNLOAD_DIR = 'downloads'; | |
| // Check every 5 minutes | |
| const CHECK_INTERVAL = 5 * 60 * 1000; | |
| const app = express(); | |
| const PORT = process.env.PORT || 7860; | |
| // ========================= | |
| // CREATE DOWNLOAD FOLDER | |
| // ========================= | |
| if (!fs.existsSync(DOWNLOAD_DIR)) { | |
| fs.mkdirSync(DOWNLOAD_DIR); | |
| } | |
| // ========================= | |
| // READ OLD DATA | |
| // ========================= | |
| let oldData = {}; | |
| if (fs.existsSync(DATA_FILE)) { | |
| oldData = JSON.parse( | |
| fs.readFileSync(DATA_FILE, 'utf-8') | |
| ); | |
| } | |
| // ========================= | |
| // URLS | |
| // ========================= | |
| const urls = [ | |
| { | |
| name: "Guidelines and Help Files", | |
| url: | |
| "https://admissions.jnvuiums.in/ViewAllNews.aspx?type=D&Heading=Guidelines%20and%20Help%20Files%20(Download%20Section)", | |
| selector: | |
| "a[href*='DownloadAttachment']" | |
| }, | |
| { | |
| name: "News and Events", | |
| url: | |
| "https://admissions.jnvuiums.in/ViewAllNews.aspx?type=N&&Heading=News%20and%20Events", | |
| selector: | |
| "ul.important_announcement li:nth-of-type(2)" | |
| }, | |
| { | |
| name: "Time Table", | |
| url: | |
| "https://erp.jnvuiums.in/CollegePortal/General_Inst_Notification.aspx?type=O&&Heading=Examination%20Time%20Table" | |
| }, | |
| { | |
| name: "General Instructions and Notification", | |
| url: | |
| "https://erp.jnvuiums.in/CollegePortal/General_Inst_Notification.aspx?type=G&&Heading=General%20Instructions%20and%20Notification" | |
| } | |
| ]; | |
| // ========================= | |
| // MAIN FUNCTION | |
| // ========================= | |
| async function startChecking() { | |
| const browser = await chromium.launch({ | |
| headless: true, | |
| args: [ | |
| '--no-sandbox', | |
| '--disable-setuid-sandbox' | |
| ] | |
| }); | |
| const context = await browser.newContext({ | |
| acceptDownloads: true | |
| }); | |
| const page = await context.newPage(); | |
| // ========================= | |
| // SCRAPE FUNCTION | |
| // ========================= | |
| async function scrapeItem(item) { | |
| console.log("\n=============================="); | |
| console.log("CHECKING:", item.name); | |
| console.log("==============================\n"); | |
| await page.goto(item.url, { | |
| waitUntil: 'networkidle' | |
| }); | |
| await page.waitForTimeout(3000); | |
| // ========================= | |
| // NEWS SECTION | |
| // ========================= | |
| if (item.name === "News and Events") { | |
| await page.waitForSelector(item.selector); | |
| const latestText = await page | |
| .locator(item.selector) | |
| .innerText(); | |
| console.log("LATEST NEWS:\n"); | |
| console.log(latestText); | |
| const oldTitle = | |
| oldData[item.name]?.title; | |
| if (oldTitle !== latestText) { | |
| console.log("\n🔥 NEW NEWS UPDATE FOUND 🔥"); | |
| console.log("\nOLD :"); | |
| console.log(oldTitle || "No old data"); | |
| console.log("\nNEW :"); | |
| console.log(latestText); | |
| oldData[item.name] = { | |
| title: latestText | |
| }; | |
| } | |
| else { | |
| console.log("\n✅ NO NEW NEWS UPDATE"); | |
| } | |
| return; | |
| } | |
| // ========================= | |
| // GUIDELINES SECTION | |
| // ========================= | |
| if (item.name === "Guidelines and Help Files") { | |
| const element = page | |
| .locator(item.selector) | |
| .first(); | |
| const title = await element.innerText(); | |
| console.log("TITLE :\n"); | |
| console.log(title); | |
| const oldTitle = | |
| oldData[item.name]?.title; | |
| if (oldTitle !== title) { | |
| console.log("\n🔥 NEW UPDATE FOUND 🔥"); | |
| console.log("\nOLD :"); | |
| console.log(oldTitle || "No old data"); | |
| console.log("\nNEW :"); | |
| console.log(title); | |
| const [ download ] = await Promise.all([ | |
| page.waitForEvent('download'), | |
| element.click() | |
| ]); | |
| const fileName = | |
| download.suggestedFilename(); | |
| const savePath = path.join( | |
| DOWNLOAD_DIR, | |
| fileName | |
| ); | |
| await download.saveAs(savePath); | |
| console.log("\n✅ PDF DOWNLOADED"); | |
| console.log("📁 SAVED:"); | |
| console.log(savePath); | |
| oldData[item.name] = { | |
| title: title | |
| }; | |
| } | |
| else { | |
| console.log("\n✅ NO NEW UPDATE"); | |
| } | |
| return; | |
| } | |
| // ========================= | |
| // TIME TABLE + NOTIFICATION | |
| // ========================= | |
| const row = page.locator( | |
| '#ctl00_ContentPlaceHolder1_GridData tr' | |
| ).nth(1); | |
| const title = await row | |
| .locator('td') | |
| .nth(1) | |
| .innerText(); | |
| const element = row.locator( | |
| 'a[id*="LnkBtnDownload"]' | |
| ); | |
| console.log("TITLE :\n"); | |
| console.log(title); | |
| const oldTitle = | |
| oldData[item.name]?.title; | |
| if (oldTitle !== title) { | |
| console.log("\n🔥 NEW UPDATE FOUND 🔥"); | |
| console.log("\nOLD :"); | |
| console.log(oldTitle || "No old data"); | |
| console.log("\nNEW :"); | |
| console.log(title); | |
| const [ download ] = await Promise.all([ | |
| page.waitForEvent('download'), | |
| element.click() | |
| ]); | |
| const fileName = | |
| download.suggestedFilename(); | |
| const savePath = path.join( | |
| DOWNLOAD_DIR, | |
| fileName | |
| ); | |
| await download.saveAs(savePath); | |
| console.log("\n✅ PDF DOWNLOADED"); | |
| console.log("📁 SAVED:"); | |
| console.log(savePath); | |
| oldData[item.name] = { | |
| title: title | |
| }; | |
| } | |
| else { | |
| console.log("\n✅ NO NEW UPDATE"); | |
| } | |
| } | |
| // ========================= | |
| // LOOP ALL URLS | |
| // ========================= | |
| for (const item of urls) { | |
| try { | |
| await scrapeItem(item); | |
| } | |
| catch (err) { | |
| console.log("\n❌ ERROR IN:", item.name); | |
| console.log(err.message); | |
| } | |
| } | |
| // ========================= | |
| // SAVE JSON | |
| // ========================= | |
| fs.writeFileSync( | |
| DATA_FILE, | |
| JSON.stringify(oldData, null, 2) | |
| ); | |
| console.log("\n=============================="); | |
| console.log("✅ ALL CHECK COMPLETE"); | |
| console.log("==============================\n"); | |
| await browser.close(); | |
| } | |
| // ========================= | |
| // FIRST RUN | |
| // ========================= | |
| startChecking(); | |
| // ========================= | |
| // AUTO CHECK EVERY 5 MIN | |
| // ========================= | |
| setInterval(async () => { | |
| console.log("\n⏳ CHECKING AGAIN...\n"); | |
| await startChecking(); | |
| }, CHECK_INTERVAL); | |
| // ========================= | |
| // EXPRESS SERVER | |
| // ========================= | |
| app.get('/', (req, res) => { | |
| res.send('JNVU Monitor Running ✅'); | |
| }); | |
| app.listen(PORT, () => { | |
| console.log(`Server running on port ${PORT}`); | |
| }); |