Code: FeedGrowler: Growl Notifications for RSS/Atom Feeds

This is a quick little PHP script that will allow you to monitor a source feed (currently only RSS format, although easy to add support for other formats), and notify you via a simple Growl notification bubble when it changes. It assumes a lot, is prone to false-positives and is generally very “unrobust”, but hey — it works, and it scratches an itch.

NOTE: This is a command-line PHP script, it is NOT intended to be run from a browser at all. You execute it from Terminal or via some other automated/programmatic means.


  • Growl installed and working
  • growlnotify installed and referenced in your path
  • You’re on a Mac (duh – Growl is only available for Macs)
  • PHP 5+ (with SimpleXML enabled, the ability to file_get_contents() on a URL, and access to run system comments via backticks — you probably don’t have this on a shared host, but you probably do on your own computer)
  • Ability to set up a cron entry to run this script periodically
Custom icons for a WordPress feed, and a Twitter search.

Custom icons for a WordPress feed, and a Twitter search.


  1. Copy the code from below into a file named “feedgrowler.php” somewhere on your computer.
  2. Allow execute permissions on that file:
    $ chmod +x feedgrowler.php
  3. Change the first line to include the full path to your PHP executable. You can usually get this by typing:
    $ which php
  4. Change the CACHE_DIR constant at the top of the file to a path that the script will have write access to, and where it can write cache files (one per different feed URL you run it against)
  5. If you’d like to use a different icon (perhaps a sexy feed icon?) then you can go and download it, then specify the path to it in the ICON_IMAGE_PATH constant (optional, will use Automator’s icon if not set, or you can specify the path to a custom icon as the third parameter from the command line)
  6. Do a trial run with something like this (to be safe, add double-quotes around your feed URL, especially if it contains ? or &):
    $ ./feedgrowler.php ""
  7. Now that you’ve run it once, it should have automatically registered “FeedGrowler” as an application with Growl. Go to your System Preferences and configure how you want your notifications to appear. I use “Smoke”, but you could use something like “Speech” to have new entries read out to you if you’re into that kind of thing. FeedGrowler attempts to set its notifications to “Sticky” so you need to click them to make them go away.
  8. Assuming that works, you’re ready to add a crontab entry to check your feed automatically.
    $ crontab -e
  9. Add a line that looks something like the following. Make sure you replace /path/to/feedgrowler with the full path to wherever you saved the feedgrowler.php script. This entry will check the feed once per hour. Read up on cron to see how to change this (and don’t hammer someone’s server by checking too often or you’re likely to get banned!). In case you’re wondering, the last part of this line tells cron to completely discard any direct output from FeedGrowler. If your URL contains a percent sign (%) then make sure you escape it by putting a backslash in front of it (\%) or cron will get confused:
    0 * * * * /Users/beau/utils/feedgrowler.php "" >> /dev/null 2>&1
  10. If you’d like to get notifications from more than one feed, then add more lines like the one above, changing out the feed URL each time.
  11. Sit back and relax, and wait for your notifications to pop up.
Notification of a change on the WordPress Trac timeline.

Notification of a change on the WordPress Trac timeline.

Here’s my current copy of FeedGrowler:


// No trailing slash, the user that runs the script must be able to write here.
define( 'CACHE_DIR', '/Users/beau/util/cache' );

// This image file is used if none is specified on the command line. If it's empty,
// then we'll use's icon (friendly robot!)
define( 'ICON_IMAGE_PATH', '/Users/beau/util/feed-icons/feed.png' );

// Supply the URL (quoted if it contains any querystring/weird chars) as the first parameter.
if ( empty( $argv[1] ) ) {
	echo "Usage " . $argv[0] . " URL_TO_CHECK [ICON_PATH]\n";

$feed = simplexml_load_file( trim( $argv[1] ) );

if ( !$feed ) {
	echo "Failed to load feed file.\n";

if ( count( $feed->channel->item ) ) {
	// RSS feed
	$post = $feed->channel->item[0];
	$title = $post->title;
	$body  = $post->description;
} else	if ( count( $feed->entry ) ) {
	// Atom feed
	$post = $feed->entry[0];
	$title = $post->title;
	$body  = $post->content;
} else {
	echo "Sorry, I don't know how to read that feed format.\n";

// Where this URL's content would be cached
$cache_file = CACHE_DIR . '/' . md5( $argv[1] ) . '.cache';

// Check the cache against the most recent post
if ( @file_get_contents( $cache_file) == $title . "\n" . $body ) {
	exit; // Same as last time.

// No cache hit, so overwrite the cache with new content
$fh = @fopen( $cache_file, 'wb' );
if ( $fh ) {
	fwrite( $fh, $title . "\n" . $body);
	fclose( $fh );

// Clean up strings for Growl
$title = escapeshellarg( trim( strip_tags( html_entity_decode( $title ) ) ) );
$body  = escapeshellarg( trim( substr( preg_replace('/\s+/is', ' ', strip_tags( html_entity_decode( $body ) ) ), 0, 300 ) ) );

// Figure out the icon to use
if ( !empty( $argv[2] ) ) {
	$icon = '--image ' . escapeshellarg( $argv[2] );
} else if ( defined( 'ICON_IMAGE_PATH' ) && '' != ICON_IMAGE_PATH ) {
	$icon = '--image ' . escapeshellarg( ICON_IMAGE_PATH );
} else {
	$icon = '-a ""';

// Trigger notification
`/usr/local/bin/growlnotify -n "FeedGrowler" -s $icon -t $title -m $body`;