├── .gitignore ├── example.env ├── package.json ├── README.md └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | env 3 | .env -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | COOKIE= 2 | URI= 3 | GROUP_ID= -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nsut", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@playwright/test": "^1.42.1", 14 | "@whiskeysockets/baileys": "^6.6.0", 15 | "axios": "^1.6.8", 16 | "cheerio": "^1.0.0-rc.12", 17 | "dotenv": "^16.4.5", 18 | "express": "^4.19.2", 19 | "fs": "^0.0.1-security", 20 | "mongodb": "^6.5.0", 21 | "node-schedule": "^2.1.1", 22 | "path": "^0.12.7", 23 | "qrcode-terminal": "^0.12.0", 24 | "uuid": "^9.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notification Bot 2 | 3 | ## Overview 4 | This Notification Bot is designed to scrape notifications from https://imsnsit.org/imsnsit/notifications.php , store them in a MongoDB database, and send alerts via WhatsApp. It's particularly useful for staying updated with important announcements or notices without manual checking. 5 | 6 | ![image](https://github.com/user-attachments/assets/328677bf-aef5-4f9a-8fd8-5212770db5c8) 7 | 8 | ## Features 9 | - Web scraping of notifications from a target website 10 | - MongoDB integration for storing notification data 11 | - WhatsApp messaging for real-time alerts 12 | - Automated PDF download and sharing 13 | - Scheduled scraping at regular intervals 14 | - Express server for potential API endpoints 15 | 16 | ## Technologies Used 17 | - Node.js 18 | - Express.js 19 | - MongoDB 20 | - WhatsApp Web API (via @whiskeysockets/baileys) 21 | - Cheerio for web scraping 22 | - Axios for HTTP requests 23 | - node-schedule for job scheduling 24 | 25 | ## Prerequisites 26 | - Node.js (v12 or later recommended) 27 | - MongoDB instance 28 | - WhatsApp account for the bot 29 | 30 | ## Setup 31 | 1. Clone the repository: 32 | ``` 33 | git clone https://github.com/anshaneja5/NSUTALERTBOT.git 34 | cd NSUTALERTBOT 35 | ``` 36 | 37 | 2. Install dependencies: 38 | ``` 39 | npm install 40 | ``` 41 | 42 | 3. Set up environment variables: 43 | Create a `.env` file in the root directory with the following variables: 44 | ``` 45 | URI=your_mongodb_uri 46 | GROUP_ID=your_whatsapp_group_id 47 | COOKIE=your_website_cookie 48 | ``` 49 | 50 | 4. Run the bot: 51 | ``` 52 | node index.js 53 | ``` 54 | 55 | ## Configuration 56 | - Scraping interval: Modify the `schedule.scheduleJob()` call in `index.js` to change how often the bot checks for new notifications. 57 | 58 | ## WhatsApp Setup 59 | 1. Run the bot for the first time. 60 | 2. Scan the QR code printed in the console with your WhatsApp account. 61 | 3. The bot will now be connected to your WhatsApp account. 62 | 63 | ## Contributing 64 | Contributions are welcome! Please feel free to submit a Pull Request. 65 | 66 | ## License 67 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 68 | 69 | ## Disclaimer 70 | This bot is for educational purposes only. Ensure you have permission to scrape the target website and comply with all relevant terms of service and legal requirements. 71 | 72 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const cheerio = require('cheerio'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const schedule = require('node-schedule'); 6 | const { MongoClient } = require('mongodb'); 7 | const { DisconnectReason, useMultiFileAuthState } = require("@whiskeysockets/baileys"); 8 | const makeWASocket = require("@whiskeysockets/baileys").default; 9 | const specialCharsRegex = /[\/|.:"\s-]/g; 10 | const env = require('dotenv').config(); 11 | 12 | const express = require('express'); 13 | const app = express(); 14 | const port = 8000; 15 | 16 | app.get('/', (req, res) => { 17 | res.send('Hello, World!'); 18 | }); 19 | 20 | // Start the server 21 | app.listen(port, () => { 22 | console.log(`Server is running on http://localhost:${port}`); 23 | }); 24 | 25 | let waSocket; 26 | 27 | async function connectionLogic() { 28 | try { 29 | const { state, saveCreds } = await useMultiFileAuthState("auth-info-baileys"); 30 | waSocket = makeWASocket({ 31 | printQRInTerminal: true, 32 | auth: state, 33 | defaultQueryTimeoutMs:0, 34 | keepAliveIntervalMs: 10000, 35 | connectTimeoutMs:60000, 36 | syncFullHistory:true, 37 | markOnlineOnConnect:true, 38 | emitOwnEvents:true, 39 | }); 40 | 41 | waSocket.ev.on("connection.update", async (update) => { 42 | const { connection, lastDisconnect, qr } = update || {}; 43 | 44 | if (qr) { 45 | console.log(qr); 46 | } 47 | 48 | if (connection === "close") { 49 | const shouldReconnect = 50 | lastDisconnect?.error?.output?.statusCode !== DisconnectReason.loggedOut; 51 | 52 | if (shouldReconnect) { 53 | await connectionLogic(); 54 | } 55 | } 56 | }); 57 | waSocket.ev.on("creds.update", saveCreds); 58 | 59 | //this is the code to find the groupid 60 | // waSocket.ev.on("messages.upsert", (chatUpdate) => { 61 | // msg = chatUpdate.messages[0] 62 | // console.log(msg.key.remoteJid) 63 | // }); 64 | } catch (error) { 65 | console.error('Error in connectionLogic:', error); 66 | } 67 | } 68 | 69 | async function sendWhatsAppMessageWhenPDF(message, path,notificationText) { 70 | try { 71 | if (!waSocket) { 72 | console.log('Socket connection not established. Message not sent.'); 73 | return; 74 | } 75 | const id = process.env.GROUP_ID; 76 | console.log("id",id); 77 | await waSocket.sendMessage(id, { text: message }); 78 | await waSocket.sendMessage(id, { 79 | document: fs.readFileSync(path), 80 | mimetype: 'application/pdf', 81 | fileName: `${notificationText}` 82 | }); 83 | fs.unlink(path, (err) => { 84 | if (err) { 85 | console.error('Error deleting file:', err); 86 | return; 87 | } 88 | console.log('File deleted successfully'); 89 | }); 90 | console.log("WhatsApp messages sent successfully!"); 91 | } catch (error) { 92 | console.log("Error sending WhatsApp messages:", error); 93 | } 94 | } 95 | 96 | async function sendWhatsAppMessageWhenDrive(message) { 97 | try { 98 | if (!waSocket) { 99 | console.log('Socket connection not established. Message not sent.'); 100 | return; 101 | } 102 | const id = process.env.GROUP_ID; 103 | await waSocket.sendMessage(id, { text: message }); 104 | console.log("WhatsApp messages sent successfully!"); 105 | } catch (error) { 106 | console.log("Error sending WhatsApp messages:", error); 107 | } 108 | } 109 | 110 | 111 | async function connectToMongoDB() { 112 | const uri = process.env.URI; 113 | const client = new MongoClient(uri); 114 | try { 115 | await client.connect(); 116 | console.log("Connected to MongoDB"); 117 | return client; 118 | } catch (error) { 119 | console.error("Error connecting to MongoDB:", error); 120 | throw error; 121 | } 122 | } 123 | 124 | // Function to close MongoDB connection 125 | async function closeMongoDBConnection(client) { 126 | try { 127 | await client.close(); 128 | console.log("MongoDB connection closed"); 129 | } catch (error) { 130 | console.error("Error closing MongoDB connection:", error); 131 | throw error; 132 | } 133 | } 134 | 135 | // Function to insert a new notification into MongoDB 136 | async function insertNotification(client, notification,href) { 137 | try { 138 | const dbName = "notificationsDB"; 139 | const collectionName = "notifications"; 140 | const db = client.db(dbName); 141 | const notificationsCollection = db.collection(collectionName); 142 | 143 | if(notification.path != ''){ 144 | const message = `📢 New Notice Alert 🚨\n📅 Date: ${notification.date}\n🍀 ${notification.notificationPublishedBy}\n\n💫 Notification: ${notification.notificationText}`; 145 | sendWhatsAppMessageWhenPDF(message,notification.path,notification.notificationText); 146 | await notificationsCollection.insertOne(notification); 147 | console.log("Notification inserted into MongoDB:", notification); 148 | } 149 | else{ 150 | const message = `📢 New Notice Alert 🚨\n📅 Date: ${notification.date}\n🍀 ${notification.notificationPublishedBy}\n\n💫 Notification: ${notification.notificationText}\nLink: ${href}`; 151 | sendWhatsAppMessageWhenDrive(message); 152 | await notificationsCollection.insertOne(notification); 153 | console.log("Notification inserted into MongoDB:", notification); 154 | } 155 | } catch (error) { 156 | console.error("Error inserting notification into MongoDB:", error); 157 | throw error; 158 | } 159 | } 160 | 161 | async function downloadFile(client, notificationText, notificationPublishedBy, date) { 162 | try { 163 | const response = await axios.get('https://www.imsnsit.org/imsnsit/notifications.php', { 164 | headers: { 165 | Cookie: process.env.COOKIE, // Include cookie in the request headers 166 | Referer: 'https://www.imsnsit.org/imsnsit/notifications.php' // Include referer in the request headers 167 | } 168 | }); 169 | const html = response.data; 170 | 171 | const $ = cheerio.load(html); 172 | 173 | // Find all anchor tags containing the specified notification text 174 | const anchorTag = $('a font').filter(function() { 175 | return $(this).text().trim().includes(notificationText); 176 | }).closest('a'); // Select the parent tag 177 | // Iterate through each anchor tag 178 | anchorTag.each(async (index, element) => { 179 | const href = $(element).attr('href'); 180 | if (href && href.includes('plum_url')) { 181 | try { 182 | const filePath = await downloadPDF(href,notificationText); 183 | // Insert notification into MongoDB with file path 184 | const notification = { 185 | date: date, 186 | notificationPublishedBy, 187 | notificationText, 188 | path: filePath, 189 | }; 190 | await insertNotification(client, notification, ""); 191 | console.log(`File saved at: ${filePath}`); 192 | } catch (error) { 193 | console.error("Error downloading PDF:", error); 194 | } 195 | }else if((href && href.includes('drive.google.com')) || (href && href.includes('docs.google.com')) ){ 196 | const notification = { 197 | date: date, 198 | notificationPublishedBy, 199 | notificationText, 200 | path: "" 201 | }; 202 | await insertNotification(client, notification, href); 203 | } 204 | }); 205 | } catch (error) { 206 | console.error("Error occurred:", error); 207 | } 208 | } 209 | 210 | async function downloadPDF(url,notificationText) { 211 | const response = await axios({ 212 | method: 'GET', 213 | url: url, 214 | responseType: 'stream', 215 | headers: { 216 | Cookie: process.env.COOKIE, // Include cookie in the request headers 217 | Referer: 'https://www.imsnsit.org/imsnsit/notifications.php' // Include referer in the request headers 218 | } 219 | }); 220 | // const uniqueId = uuidv4(); 221 | notificationText = notificationText.replace(specialCharsRegex, ''); 222 | // Extract the file name from the URL 223 | const fileName = `${notificationText}.pdf`; 224 | // Specify the download directory 225 | const downloadDir = path.join(__dirname, 'notice'); 226 | 227 | // Create the directory if it doesn't exist 228 | if (!fs.existsSync(downloadDir)) { 229 | fs.mkdirSync(downloadDir); 230 | } 231 | 232 | // Specify the file path 233 | const filePath = path.join(downloadDir, fileName); 234 | 235 | // Write the stream to the file 236 | response.data.pipe(fs.createWriteStream(filePath)); 237 | 238 | // Return the file path 239 | return new Promise((resolve, reject) => { 240 | response.data.on('end', () => { 241 | resolve(filePath); 242 | }); 243 | 244 | response.data.on('error', (error) => { 245 | reject(error); 246 | }); 247 | }); 248 | } 249 | 250 | async function scrapeNotifications() { 251 | const client = await connectToMongoDB(); 252 | 253 | try { 254 | const response = await axios.get('https://www.imsnsit.org/imsnsit/notifications.php'); 255 | const html = response.data; 256 | 257 | const $ = cheerio.load(html); 258 | 259 | // Select each table row (notification) from the desired table 260 | $('form > table[width="80%"] tbody tr').each(async (index, element) => { 261 | const date = $(element).find('td:first-child font').text().trim(); 262 | const notificationPublishedBy = $(element).find('td:nth-child(2) font').last().text().trim(); 263 | const notificationText = $(element).find('td:nth-child(2) font').first().text().trim(); 264 | const notificationLink = $(element).find('td:nth-child(2) a').attr('href'); 265 | 266 | // Check if notification has all necessary details 267 | if (date && notificationPublishedBy && notificationText && notificationLink) { 268 | // Check if the notification already exists in the database 269 | const existingNotification = await client.db("notificationsDB").collection("notifications").findOne({ 270 | date: date, 271 | notificationPublishedBy: notificationPublishedBy, 272 | notificationText: notificationText, 273 | }); 274 | 275 | // If the notification doesn't exist, add it to the database 276 | if (!existingNotification) { 277 | // Push download promise to array 278 | downloadFile(client, notificationText, notificationPublishedBy, date); 279 | } 280 | } 281 | }); 282 | } catch (error) { 283 | console.error('Error scraping notifications:', error); 284 | } finally { 285 | // Close MongoDB connection 286 | // await client.close(); 287 | } 288 | } 289 | 290 | 291 | connectionLogic(); 292 | 293 | // Schedule scraping task every 60 minutes 294 | const scrapingJob = schedule.scheduleJob('*/60 * * * *', () => { 295 | console.log('Scraping for new notifications...'); 296 | scrapeNotifications(); 297 | }); 298 | 299 | // Schedule scraping task every 30 seconds, I used this for testing 300 | // const scrapingJob = schedule.scheduleJob('*/30 * * * * *', () => { 301 | // console.log('Scraping for new notifications...'); 302 | // scrapeNotifications(); 303 | // }); --------------------------------------------------------------------------------