Skip to content

Instantly share code, notes, and snippets.

@aurmil
Last active August 3, 2025 07:27
Show Gist options
  • Save aurmil/6587cc6c31a903d25919389edde101b7 to your computer and use it in GitHub Desktop.
Save aurmil/6587cc6c31a903d25919389edde101b7 to your computer and use it in GitHub Desktop.
Scheduled web page changes watcher and notifier
<?php
class Watch
{
public function __construct(
private readonly string $logFilePath,
private readonly string $cacheFilePath,
) {
}
public function watch(string $url, string $attribute, string $value): void {
// call remote URL
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentLength = curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD);
curl_close($ch);
$this->log("URL: $url ; HTTP code: $httpCode ; Content size: $contentLength Bytes");
if (200 !== $httpCode) {
return;
}
// load DOM and search element
$dom = new DOMDocument();
@$dom->loadHTML($response);
if ('id' === $attribute) {
$element = $dom->getElementById($value);
} else {
$xpath = new DOMXPath($dom);
$elements = $xpath->query("//*[@$attribute='$value']");
if ($elements->length) {
$element = $elements->item(0);
}
}
if (empty($element)) {
$this->log("Error: no element found with attribute $attribute=$value");
return;
}
$elementContent = $dom->saveHTML($element);
if (!strlen(trim((string) $elementContent))) {
$this->log("Error: no content found in element with attribute $attribute=$value.");
return;
}
// generate and compare hashs
$newHash = hash('sha256', $elementContent);
$hashFile = $this->cacheFilePath;
$hashs = file_exists($hashFile) ? (array)json_decode((string)file_get_contents($hashFile), true) : [];
$hashKey = md5($url);
$storedHash = $hashs[$hashKey] ?? '';
if (hash_equals($storedHash, $newHash)) {
$this->log("Hashs are equals");
} else {
$this->log("Hashs are different");
// save new hash
$hashs[$hashKey] = $newHash;
file_put_contents($hashFile, json_encode($hashs));
// send notification email
if (
!$this->sendEmail(
EMAIL,
null,
EMAIL,
null,
'Watch - Found a change',
"A change was found on page $url"
)
) {
$this->log("Error: email not sent");
}
}
}
private function log(string $message): void
{
file_put_contents(
$this->logFilePath,
sprintf('%s %s%s', date('Y-m-d H:i:s'), $message, PHP_EOL),
FILE_APPEND
);
}
private function sendEmail(
string $senderEmail,
?string $senderName,
string $recipientEmail,
?string $recipientName,
string $subject,
string $body
): bool {
$recipient = $recipientEmail;
if ($recipientName) {
$recipient = "$recipientName <$recipientEmail>";
}
$subject = '=?utf-8?b?' . base64_encode($subject) . '?=';
$sender = $senderEmail;
if ($senderName) {
$sender = '=?utf-8?b?' . base64_encode($senderName) . "?= <$senderEmail>";
}
$headers = [
'MIME-Version' => '1.0',
'Content-Type' => 'text/plain;charset=utf-8',
'From' => $sender,
'Reply-To' => $sender,
'X-Mailer' => 'PHP/' . phpversion()
];
return mail($recipient, $subject, $body, $headers);
}
}
const EMAIL = '...';
$rootDirPath = dirname(__DIR__);
$watchDirPath = $rootDirPath . '/watch';
$watch = new Watch($watchDirPath . '/watch.log', $watchDirPath . '/cache.json');
$watch->watch('...', 'id', '...');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment