Stream Travis-CI updates into notifications center

OSX 10.8 comes with a new feature i've been waiting for - Notifications Center. Its pretty much the same notifications panel that already exists on iOS platform. It also seems like a logical way to organize application notifications. Technical part of it is the same as existing apple push notifications service except it offers local delivery method. I wanted to hook up build notifications from Travis-CI. It uses Pusher service to handle all events, plus it has few ruby libraries to work with.

Eloy Durán created a very simple terminal tool to send local notifications called terminal-notifier. It does exactly what i needed and the source code is available so you can customize application icon and name if you want to.

To send a notification all you have to do is run command:

$ ./terminal-notifier_1.1.0/terminal-notifier.app/Contents/MacOS/terminal-notifier group title message

All i had to do it write some ruby code (didnt want to mess with Objective-C) that gets an event stream from public travis-ci and call notifier application.

Here is some code (travis.rb):

require 'pusher-client'
require 'json'

class Travis
  def initialize(pusher_token)
    @socket = PusherClient::Socket.new(pusher_token)
  end

  def parse_build(payload)
    JSON.parse(payload)
  end

  def make_notification(data)
    result = data['build']['result'] == 0 ? 'PASSED' : 'FAILED'
    title = data['repository']['slug']
    message =  "Build ##{data['repository']['last_build_number']} #{result}"
    {:title => title, :message => message}
  end

  def start
    @socket.subscribe('common')
    @socket['common'].bind('build:finished') do |data|
      n = make_notification(parse_build(data))
      add_notification(n[:title], n[:message])
    end
    @socket.connect
  end

  def add_notification(title, message)
    `./terminal-notifier_1.1.0/terminal-notifier.app/Contents/MacOS/terminal-notifier "#{title}" "#{title}" "#{message}"`
  end
end

travis = Travis.new('23ed642e81512118260e')
travis.start

Unfortunately, the original pusher-client gem is broken, so you'd have to create a Gemfile with following content:

source :rubygems 
gem 'pusher-client', :git => 'git://github.com/pusher/pusher-ruby-client.git'

Then run:

bundle install

This will get you customized gem. Fire up the client by running:

bundle exec ruby travis.rb

Check out the result:

Apple Notifications Center

You can also grab the source code here - https://gist.github.com/3186275

Update

Eloy Durán had recently released a ruby gem terminal-notifier that adds ability to send notifications straight from ruby. Installation is easy:

gem install terminal-notifier

I also made an update to the original script, by clicking on notification message it opens up url to travis build details. Updated code:

require 'terminal-notifier'
require 'pusher-client'
require 'json'
require 'pp'

class Travis
  attr_accessor :watchlist

  # Initialize a new connection to travis feed
  #
  # publisher_token - Valid pusher token
  # watchlist - Array or repositories to watch
  # 
  def initialize(pusher_token, watchlist=[])
    @socket    = PusherClient::Socket.new(pusher_token)
    @watchlist = watchlist
  end

  def start
    @socket.subscribe('common')
    @socket['common'].bind('build:finished') do |data|
      n = make_notification(parse_build(data))
      if watchlist.empty?
        add_notification(n)
      else
        add_notification(n) if watchlist.include?(n[:title])
      end
    end
    @socket.connect
  end

  private

  def parse_build(payload)
    JSON.parse(payload)
  end

  def make_notification(data)
    id      = data['build']['id']
    number  = data['repository']['last_build_number']
    result  = data['build']['result'] == 0 ? 'PASSED' : 'FAILED'
    title   = data['repository']['slug']
    message =  "Build ##{number} #{result}"
    url     = "http://travis-ci.org/#!/#{title}/builds/#{id}"
    {:title => title, :message => message, :url => url}
  end

  def add_notification(n)
    TerminalNotifier.notify(n[:message], :title => n[:title], :open => n[:url])
  end
end

travis = Travis.new('23ed642e81512118260e')
travis.start

You can also specify a list of watched repositories by providing an additional parameter:

Travis.new('token', ['rails/rails'])
# or directly
travis = Travis.new('token')
travis.watchlist = ['rails/rails']