Skip to content

Instantly share code, notes, and snippets.

@AndrewMohawk
Created December 19, 2024 18:53
Show Gist options
  • Save AndrewMohawk/03060959a2b7f91e2a58b6375b5ff9df to your computer and use it in GitHub Desktop.
Save AndrewMohawk/03060959a2b7f91e2a58b6375b5ff9df to your computer and use it in GitHub Desktop.
Webcontent change monitor
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services<restofurl>"
npm init -y
npm install node-fetch@2 chalk@4
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