12 Hacks of Christmas – Day 3: Santa CLI

December 14, 2014
Written by

day2

Hi folks! Today we on the devangel crew wanted to sprinkle the 12 Hacks of Christmas with some holiday cheer. The following hack started out as a Santa location hack. Then we found out that there is no such thing as a ‘Santa API’, which means first we’re going to have build that API and then maybe next year we’ll build a tool for tracking Santa in real-time via Twilio.

Instead Jarod, one of the parents on our team, put together a fun little command-line tool for sending secret messages to festive little recipients. Let us know what you think on Twitter with the hashtag #12HacksOfChristmas.

Santa’s Helper – Command Line tool using Ruby-gem, Thor and Twilio

Growing up in my house during Christmas was a delight. Every year we’d put out a tray of cookies on Christmas Eve, hang the stockings and try to sleep quietly upstairs as Santa visited our house and delivered gifts. It has always been a special night but now that I am a new parent I appreciate the magic of Santa even more.

When I began thinking about how parents like myself might want to interact with the North Pole I started by making a list of requirements and concerns:

  • It needed to seem magical (simply texting a phone number might not be magical)
  • It needed to be hard to find for a child (ie; not a website)
  • It shouldn’t be a phone-to-phone hack (since we don’t want Mom’s phone chiming when little johnny thinks he is talking to the North Pole).
  • It needs to be extendable by the community

In the end I decided the best implementation would be a ruby-gem command-line tool where hacker parents could interact discreetly with the North Pole. This would allow for magical experiences, is pretty hard for a child to stumble upon and gives us a tool that is easily extendable by the community. Luckily I had been looking for an excuse to build something using the Thor gem… and a command line interface to Santa’s helpers seemed the perfect console for creating magic :)

In this post I’ll show you how to build the SantaCLI from start to finish, but thats not my real goal. The real goal of this post is to equip you with enough information to inspire you to build an experience customized for your own family. So check out the code and start tweaking!

Demo:

Text +1 860-947-HOHO(4646) to ping the North Pole and see what Santa is up to.

The Code:

santas_twilio_helper on github
santas_twilio_helper on ruby-gems
Twilio: Ruby helper library, REST API, Twilio Number, Thor

Sign Up for Twilio

The North Pole is listening: command list

When I sat down to create my command line interface to the North Pole I wanted to make sure it was a two way channel. I wanted to make sure the elves had a way to communicate with my kids while also giving me a way of sending messages as an ambassador of Santa. I also wanted to give all of you hacker Moms and Dads a foundation that you could build on. Here’s the list of commands I came up with:

  • $ santa begin: this command should register you as a parent, your children’s names and your zip code. This gives you and the elves access to certain details (variables) down the road.
  • $ santa ping: ping the North Pole which should send a random message status (SMS) of what Santa is up to. This could be extended to give location updates on Christmas Day, or to send MMS pics of Santa selfies.
  • $ santa telegraph: this allows mom or dad to send a message as one of Santa’s helpers. This should have an option for a time delay.

Great, now let me show you how I built this beauty.

Before we move on, feel free to grab the code from github and follow along: https://github.com/jarodreyes/santas-twilio-helper

Creating a Ruby gem that used Twilio

Feel free to skip this section if you just want to install my santa gem and play around with it, for the rest of us, here is a quick rundown of how I chose to create the gem.

There are a few ways to create a ruby gem, including by following the ruby-gem docs. I decided to use Bundler to bundle up the gem. The biggest benefit to using Bundler is that it can generate all of the files needed for a gem and with a few TODO notes to help you fill it out. Additionally you get some handy rake commands for building and releasing the gem when you’re finished.

First you need to install Bundler:

$ gem install bundler

Next you can generate the gem:

$ bundle gem santa_helper
    create santa_helper/Gemfile
    create santa_helper/Rakefile
    create santa_helper/LICENSE.txt
    create santa_helper/README.md
    create santa_helper/.gitignore
    create santa_helper/santa_helper.gemspec
    create santa_helper/lib/santa_helper.rb
    create santa_helper/lib/santa_helper/version.rb
    Initializating git repo in <path-to-gem>/santa_helper

Next you can open the directory you just created and fill out all of the TODOs in the .gemspec file.

# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'santa_helper/version'

Gem::Specification.new do |spec|
  spec.name          = "santa_helper"
  spec.version       = SantaHelper::VERSION
  spec.authors       = ["Jarod Reyes"]
  spec.email         = ["jarodreyes@gmail.com"]
  spec.summary       = %q{TODO: Write a short summary. Required.}
  spec.description   = %q{TODO: Write a longer description. Optional.}
  spec.homepage      = ""
  spec.license       = "MIT"

  spec.files         = `git ls-files -z`.split("\x0")
  spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
  spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 1.7"
  spec.add_development_dependency "rake", "~> 10.0"
end

Finally you need to make sure Twilio and Thor are available in your gem. This means requiring them in two places; the .gemspec and your main application. First you should add them to the .gemspec file:

spec.add_dependency 'thor', '~> 0.18'
spec.add_dependency 'twilio-ruby', '~> 3.11'

Eventually you’ll need to require them in the application.rb file but that’s getting ahead of ourselves.

In the santas_twilio_helper gem this has all been done for you so you can just install the gem now and build on top of it.

$ gem install santas_twilio_helper

Putting the fruit in the fruitcake: building the CLI using Thor.

Thor is a nifty little library. By including it we get a lot of built-in functionality that will make our minimal code act like a full fledged CLI. To give you an example of what I mean type this into the ‘santas_twilio_helper.rb’ file:

desc 'hohoho', 'Wake up the big red man'
def begin
  puts 'Ho Ho Ho! Merry Christmas to all and to all a good night!'
end

Now because we are using Bundler we can execute the command line tool using the bundle command:

$ bundle exec bin/santa hohoho

That’s all it takes to create a CLI using Thor, a set of descriptions and methods that execute code in the console.

If you’ve installed the santas_twilio_helper gem you can see a bit of Thor’s magic by typing santa help:

Screen Shot 2014-12-07 at 2.24.46 PM.png

Using Thor

The simplest way to use Thor is by creating some commands within a Thor class that are defined by their description. However, Thor also includes Actions, which are modules that interact with our local environment to give us some cool functionality.

To illustrate this example here is the begin command that intakes some input from the console and writes it to a file.

require 'thor'
require 'paint'
require 'json'
require 'twilio-ruby'

module SantasTwilioHelper
  module Cli
    class Application < Thor

      # Class constants
      @@twilio_number = ENV['TWILIO_NUMBER']
      @@client = Twilio::REST::Client.new ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN']

      include Thor::Actions

      desc 'begin', 'Register yourself as one of Santas helpers'
      def begin
        say("#{Paint["Hi I'm one of Santa's Twilio Elves, and I'm about to deputize you as an ambassador to Santa. To get started I need your name.", :red]}")
        santa_helper = ask("Parent Name: ")

        children = []
        say("Great Gumdrops. We also need your child's name to verify they are on Santa's list. ")
        child = ask("Child Name: ")
        children.push(child)

        say("Fantastic. You can always add more children by running add_child later.")
        say("Next I need to know your telephone number so Santa's helpers can get in touch with you.")
        telephone = ask("#{Paint['Telephone Number: ', :red]}")

        say("The last thing I need is your city so we can verify we have the correct location for #{child}.")
        zip_code = ask("#{Paint['Zip Code: ', :blue]}")

        data = {
          'santa_helper' => santa_helper,
          'children' => children,
          'telephone' => telephone,
          'zip_code'=> zip_code
        }

        write_file(data)

        say("#{Paint["Okay you're off to the races. You can type `santa help` at any time to see the list of available commands.", "#55C4C2"]}")
      end
    end
  end
end

Right away you can see some cool Thor functionality, by using the methods say and ask Thor will pause the shell session to prompt the user for some feedback, wait for a response and then store the response to a variable that our script can reference.

I mentioned earlier that Thor includes Actions which allow us to save input to disk among other things. One of these Actions is create_file() which I use in the function below, to save the user input from begin to the filesystem.

def write_file(data_hash)
   create_file "santarc.json", "// Your Santas Helper configuration.\n #{data_hash.to_json}", :force => true
end

I chose to write the data to a santarc file as opposed to writing to db. If you would like a more secure data store you should probably change this.

Next let’s take a look at the ping command since I believe this command has a ton of potential ways it could be extended to do amazing things.

# PING will tell us what Santa is up to.
desc 'ping', 'See where Santa is right now'
def ping
  file = File.read('messages.json')
  messages = JSON.parse(file)
  # TODO: if it is Dec. 25 we should pull from a different set of messages. Location specific
  # TODO: We should use the zip code in the profile to make sure Santa arrives here last.

  # For now Santa messages are all non-location based
  santaMs = messages['SANTA_SNIPPETS']
  a = rand(0..(santaMs.length-1))
  msg = santaMs[a]
  puts "sending message..."
  sendMessage(msg)
end

This command is pretty straight-forward as it stands, but you may want to extend it based on the TODO messages in the comments. For now it reads messages from the ‘messages.json’ file and pulls a random message. Then we call sendMessage() which actually does the job of sending the SMS with Twilio.

Before we can send a message in Ruby using Twilio all we need to do is add ‘require twilio-ruby’ to the top of application.rb

def sendMessage(msg)
          message = @@client.account.messages.create(
            :from => @@twilio_number,
            :to => phone,
            :body => msg
          )
          puts "message sent: #{msg}"
        end

This version of sendMessage() is the simplest way we can send an SMS using Twilio. To send a picture you would simply need to add a :media_url parameter that would point to a url of some media. However on github, you will find a more robust version of sendMessage() that actually appends a greeting and a sign-off from the elves.

I’ll quickly mention the ‘telegraph’ command which allows me to send a message as Santa’s Helper with a time delay (in seconds). This means I can create scenarios where I am in the kitchen cooking with the family when the ‘Santa Phone’ mysteriously delivers a custom message. Pretty sneaky ;) Try it yourself (assuming you installed the gem) with the command:

$ santa telegraph "Santa just checked his list for the 3rd time, hope you were nice today!" 240

The last bit of know-how I picked up on this journey to a gem was how to build and release the gem.

Building and releasing the ruby gem.

Before we can use the handy Rake tasks we need to save your ruby-gem credentials to your .gem file. So run:

$ curl -u your-rubygems-username https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials

Once we have done this we can run:

$ rake build && rake release

Grok’in around the christmas tree

So now we have a working CLI to interact with the North Pole, but there are a few things I would challenge you all to do. First this CLI could use a location specific hack. Since neither Google or Norad(Bing) offer an api for Santa’s location this is going to require some work on your end. But if you want to collaborate on a Santa API hit me up and we’ll get started.

I would also recommend adding a Christmas day switch to the ‘ping’ command that maybe kicks off an hourly update via SMS of where Santa is located.

Hopefully learning how I built this tool has given you a plethora ideas of how to make it even more magical. I look forward to all of the pull requests on github, but until then feel free to ping me on twitter or email and we can talk about how The Santa Clause movie with Tim Allen is actually a cultural gem.