export SLACK_WEBHOOK_URL="https://hooks.slack.com/services<restofurl>"
npm init -y
npm install node-fetch@2 chalk@4
Created
December 19, 2024 18:53
-
-
Save AndrewMohawk/03060959a2b7f91e2a58b6375b5ff9df to your computer and use it in GitHub Desktop.
Webcontent change monitor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const crypto = require("crypto"); | |
const fs = require("fs/promises"); | |
const path = require("path"); | |
const chalk = require("chalk"); | |
// Configuration | |
const CONFIG = { | |
url: "https://auth.privy.io", | |
hashFile: path.join(__dirname, "last-hash.txt"), | |
logFile: path.join(__dirname, "changes.log"), | |
checkInterval: 5 * 60 * 1000, // 5 minutes | |
slackWebhook: process.env.SLACK_WEBHOOK_URL, | |
maxCaptchaAttempts: 10, | |
}; | |
let consecutiveCaptchaCount = 0; | |
async function calculateHash(content) { | |
return crypto.createHash("sha256").update(content).digest("hex"); | |
} | |
async function getLastHash() { | |
try { | |
return await fs.readFile(CONFIG.hashFile, "utf8"); | |
} catch (error) { | |
if (error.code === "ENOENT") { | |
return null; | |
} | |
throw error; | |
} | |
} | |
async function saveHash(hash) { | |
await fs.writeFile(CONFIG.hashFile, hash); | |
} | |
async function logChange(oldHash, newHash, type = "content") { | |
const timestamp = new Date().toISOString(); | |
const logEntry = `${timestamp} - ${type} change detected\nOld hash: ${oldHash}\nNew hash: ${newHash}\n---\n`; | |
try { | |
console.log(chalk.blue("Writing to log file:", CONFIG.logFile)); | |
console.log(chalk.gray("Log entry:", logEntry)); | |
// Ensure the directory exists | |
const logDir = path.dirname(CONFIG.logFile); | |
await fs.mkdir(logDir, { recursive: true }); | |
await fs.appendFile(CONFIG.logFile, logEntry); | |
console.log(chalk.green("Successfully wrote to log file")); | |
} catch (error) { | |
console.error(chalk.red("Error writing to log file:", error.message)); | |
console.error("Full error:", error); | |
} | |
} | |
async function sendSlackNotification(oldHash, newHash, type = "content") { | |
if (!CONFIG.slackWebhook) { | |
console.log( | |
chalk.yellow("Slack webhook URL not configured. Skipping notification.") | |
); | |
return; | |
} | |
const message = { | |
text: `🔔 ${ | |
type === "captcha" | |
? "Cloudflare Captcha Alert!" | |
: "Website content change detected!" | |
}\n*URL:* ${ | |
CONFIG.url | |
}\n*Old hash:* ${oldHash}\n*New hash:* ${newHash}\n*Time:* ${new Date().toISOString()}`, | |
}; | |
try { | |
const response = await fetch(CONFIG.slackWebhook, { | |
method: "POST", | |
headers: { "Content-Type": "application/json" }, | |
body: JSON.stringify(message), | |
}); | |
if (!response.ok) { | |
throw new Error( | |
`Failed to send Slack notification: ${response.statusText}` | |
); | |
} | |
} catch (error) { | |
console.error( | |
chalk.red("Error sending Slack notification:", error.message) | |
); | |
} | |
} | |
function isCloudfareCaptchaPage(content) { | |
// Common indicators of Cloudflare captcha page | |
return ( | |
content.includes("cf-captcha-container") || | |
content.includes("cf-please-wait") || | |
content.includes("challenge-running") || | |
content.includes("cf_captcha_kind") | |
); | |
} | |
async function checkWebsite() { | |
try { | |
console.log(chalk.blue(`Checking ${CONFIG.url}...`)); | |
const response = await fetch(CONFIG.url); | |
if (!response.ok) { | |
throw new Error(`HTTP error! status: ${response.status}`); | |
} | |
const content = await response.text(); | |
// Check for Cloudflare captcha page | |
if (isCloudfareCaptchaPage(content)) { | |
consecutiveCaptchaCount++; | |
console.log( | |
chalk.yellow( | |
`Cloudflare captcha detected (${consecutiveCaptchaCount}/${CONFIG.maxCaptchaAttempts})` | |
) | |
); | |
if (consecutiveCaptchaCount >= CONFIG.maxCaptchaAttempts) { | |
console.error( | |
chalk.red( | |
`Cloudflare captcha detected ${CONFIG.maxCaptchaAttempts} times in a row!` | |
) | |
); | |
await logChange("N/A", "N/A", "captcha"); | |
await sendSlackNotification("N/A", "N/A", "captcha"); | |
consecutiveCaptchaCount = 0; // Reset after notification | |
} | |
return; | |
} | |
// Reset captcha counter if we get a normal page | |
consecutiveCaptchaCount = 0; | |
const currentHash = await calculateHash(content); | |
const lastHash = await getLastHash(); | |
if (lastHash === null) { | |
console.log(chalk.green("Initial hash stored:", currentHash)); | |
await saveHash(currentHash); | |
return; | |
} | |
if (currentHash !== lastHash) { | |
console.log(chalk.yellow("Change detected!")); | |
console.log(`Previous hash: ${lastHash}`); | |
console.log(`Current hash: ${currentHash}`); | |
console.log(chalk.blue("Attempting to log change...")); | |
await logChange(lastHash, currentHash); | |
await sendSlackNotification(lastHash, currentHash); | |
await saveHash(currentHash); | |
} else { | |
console.log(chalk.green("No changes detected.")); | |
} | |
} catch (error) { | |
console.error(chalk.red("Error:", error.message)); | |
} | |
} | |
// Main execution | |
if (!process.env.SLACK_WEBHOOK_URL) { | |
console.log( | |
chalk.yellow("Warning: SLACK_WEBHOOK_URL environment variable not set") | |
); | |
} | |
console.log(chalk.blue("Starting website monitor...")); | |
console.log(`URL: ${CONFIG.url}`); | |
console.log(`Check interval: ${CONFIG.checkInterval / 1000} seconds`); | |
// Initial check | |
checkWebsite(); | |
// Schedule regular checks | |
setInterval(checkWebsite, CONFIG.checkInterval); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment