Last active
August 3, 2025 07:27
-
-
Save aurmil/6587cc6c31a903d25919389edde101b7 to your computer and use it in GitHub Desktop.
Scheduled web page changes watcher and notifier
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
<?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