My friend Ross Wintle decided to update his MacBook to Big Sur earlier this week, an act that I feel should be described as brave. It did not go well. Anyway, because Ross is a brave soul he went out and bought a new MacBook Air with the new M1 ARM processor.
Exciting times! He is live blogging the install and you can catch day 1 of the live blog on his site. I love the idea of live blogs and so I settled down with the popcorn ready to watch his journey.
The problem was I wanted to know when the page was updated. Now, I could have reached for a multitude of services that offer this sort of thing, but this is one of those times it would be quicker to code something than Google the answer.
Version 1
Composer require guzzlehttp/guzzle
Then in a file called ross
#!/opt/asdf/php/bin/php
$response = $client->get('https://rosswintle.uk/2020/11/apple-m1-apple-silicon-day-1-live/');
if( $response->getStatusCode() == 200 ){
$hash = md5($response->getBody()->getContents() );
$current_hash = trim( @file_get_contents( 'loghash' ) );
if( $hash !== $current_hash ){
$cmd = 'display notification "Contents Changed on Ross Page" with title "Ross Update"';
exec("osascript -e '".$cmd."'");
file_put_contents( 'loghash', $hash );
}
}
It’s not very sophisticated, and I really didn’t need Guzzle. In my head Guzzle had a nice checksum helper class but actually it doesn’t; this was a figment of my imagination.
Anyway, the script:
- Makes an HTTP request to Ross’ site (using Guzzle which is a wrapper for curl), checks the status code and if it’s a 200,
- Returns the content and does a MD5 hash. I could use a range of hashing options but MD5 is fine for this.
- Next grab the contents from a file called loghash.
- Compare the two, if they don’t match then execute a command.
- In this case we are executing a piece of Apple script to trigger an Apple notification of a change.
- Finally we overwrite that loghash file.
There is probably a much better way to get notifications from this file, but that was quick and easy. This is also the point where many would say “why use PHP?”.
Why use PHP
PHP is a nice and easy scripting language, for these small tasks it’s super quick and easy to implement and modern PHP is very fast. I could have used a bunch of other languages but I very much start with PHP and if another more appropriate choice comes, then port.
Ok so we have a file, but how to actually get that polling every minute, I could demonise it but that is a pain, but might be easier using an alternative.
Now MacOS has a traditional crontab and that is the initial option I went with, but thought while I’m here I could look at Launchd which I know has its own cron system. I had in the back of my head it would be like SystemD timers. I was wrong, there is an awful lot of XML. Now if this was mission critical I would be far more interested in doing it right, but this is a couple of minutes of fun.
I very quickly decided that wasn’t fun, then I remembered Hammerspoon has timers. Maybe instead of the cron I could use Hammerspoon.
Hammerspoon
If you are a subscriber to my newsletter, and if you are not WAT! go subscribe we will wait…
Then you will know I quite like Hammerspoon which is a MacOS automation platform that allows you to automate just about anything on your Mac with Lua. Lua is a scripting language. I really like Hammerspoon. It’s that combination of simplicity but at the same time massively powerful in what you can do with it. However, you start from scratch.
One of its features is the hs.timer
functions that allows you to set countdowns or check every few minutes for an action. This was perfect for my use case so in my init.lua file in ~./hammerspoon I added
ross_timer = hs.timer.new(60, function()
hs.osascript.applescript(
'do shell script "/Users/tnash/Dev/local/rosschecker/ross > /dev/null 2>&1 &"'
)
end)
ross_timer:start()
This creates a timer that executes every 60 seconds and runs (yet again) some Apple script to run a shell script.
So now we have Hammerspoon running Lua, calling Applescript to run PHP which in turn runs Apple script to tell MacOS notifications something has changed.
Ok even I have to admit this seems a tad daft and suspecting Hammerspoon had an HTTP library it was time for a proper refactor. After all, PHP is good for the job until something is better. Hammerspoon is already running and is way more convenient.
hash = 'default'
ross_timer = hs.timer.new(60, function()
status, data, headers = hs.http.get('https://rosswintle.uk/2020/11/apple-m1-apple-silicon-day-1-live/')
current_hash = hs.hash.MD5(data)
if current_hash ~= hash then
hs.notify.new({title="Ross Update", informativeText="MacOS Page Updated"}):send()
hash = current_hash
end
end)
ross_timer:start()
Ok, so we have modified our timer; instead of calling our shell script we are making a get request, we are storing its parts in the variables status, data, headers and then comparing it with the variable hash. If they don’t match, it uses Hammerspoon notify to send me a message and then replaces the hash variable with the current hash.
So now Hammerspoon is checking Ross’ site every 60 seconds for updates, BUT I don’t want to refresh the page!
Can I get Hammerspoon to interact with Firefox and refresh the tab for me?
The quick answer is maybe, but not today.
Looking at how to implement this led me down a really interesting path of JAX and Apple Script that ultimately terminated with this bug report.
So for now, Hammerspoon is just letting me visually know about the update. HOWEVER I still wanted to see the updates when I visited, so having pinned his site in Firefox I used the Tab Auto Refresh extension set to refresh every 2 minutes and left it.
So that is how I have been hammering my friend’s site all day, keeping up with updates.
Now this probably isn’t the best solution, having sent 2 CURL requests and a partial page reload every 2 minutes, hopefully his stats are not too skewed.
I thought it was worth a write up, just because it was a fun little 5 minute project. There was probably only 2 minutes of code writing, but it went through a few refactors and it gives you an idea of some of the potential for scripting both with PHP and Hammerspoon.