SMS from the command line with Google Voice

I needed a script send an SMS today, and I found a very handy post at sudocode to send one via Google Voice, using PHP. I wanted to make it a little more command-line-friendly, so I rewrote it (ham-handedly) in Ruby and added some options parsing to it. It’s designed for — and only tested on — OS X, but may work fine elsewhere.

The code

To use it, copy the code below into a text file, save it as voicesms.rb (or download it directly using the link at the top of the code), and run chmod a+x voicesms.rb from the command line to make it executable.

#!/usr/bin/env ruby -Ku

require 'net/http'
require 'net/https'
require 'open-uri'
require 'cgi'
require 'optparse'

ACCOUNT = '' # Set to Google Voice account email for default account
PASSWORD = '' # Set to Google Voice account password for default account
NUMBERS = ['+1555444333','+1555444222'] # Set one or more numbers for default destination(s)

options = {}
optparse = OptionParser.new do|opts|
  opts.banner = "Usage: voicesms.rb -n +15554443333[,+15554442222] -m \"Message to send\" [-u Username:Password]"

  options[:numbers] = NUMBERS
  opts.on( '-n', '--numbers NUM[,NUM]', 'Phone numbers to SMS (separate multiples with comma)' ) do|numbers|
    options[:numbers] = numbers.split(',')
  end

  options[:message] = false
  opts.on( '-m', '--message MESSAGE', 'Message to send' ) do|msg|
    options[:message] = msg
  end

   options[:username] = ACCOUNT
   options[:password] = PASSWORD
   opts.on( '-u', '--user USERNAME:PASSWORD', 'Google Voice username and password' ) do|creds|
     parts = creds.split(':')
     options[:username] = parts[0]
     options[:password] = parts[1]
   end

  opts.on( '-h', '--help', 'Display this screen' ) do
    puts opts
    exit
  end
end

optparse.parse!

unless options[:message]
  puts "Message required. Use -m \"MESSAGE\"."
  puts "Enter `voicesms.rb -h` for help."
  exit
end

def postit(uri_str, data, header = nil, limit = 3)
    raise ArgumentError, 'HTTP redirect too deep' if limit == 0
    url = URI.parse(uri_str)
    http = Net::HTTP.new(url.host,443)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    response,content = http.post(url.path,data,header)
    case response
      when Net::HTTPSuccess     then content
      when Net::HTTPRedirection then postit(response['location'],data,header, limit - 1)
      else
        puts response.inspect
        response.error!
    end
end

def getit(uri_str, header, limit = 3)
    raise ArgumentError, 'HTTP redirect too deep' if limit == 0
    url = URI.parse(uri_str)
    http = Net::HTTP.new(url.host,url.port)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    response,content = http.get(url.path,header)
    case response
      when Net::HTTPSuccess     then content
      when Net::HTTPRedirection then getit(response['location'],header, limit - 1)
      else
        response.error!
    end
end

data = "accountType=GOOGLE&Email=#{options[:username]}&Passwd=#{options[:password]}&service=grandcentral&source=brettterpstra-CLISMS-2.0"
res = postit('https://www.google.com/accounts/ClientLogin',data)
if res
  authcode = res.match(/Auth=(.+)/)[1]
  header = {'Authorization' => "GoogleLogin auth=#{authcode.strip}",'Content-Length' => '0'}
  newres = getit('https://www.google.com/voice',header)
  if newres
    rnrse = newres.match(/'_rnr_se': '([^']+)'/)[1]
    options[:numbers].each {|num|
      data = "_rnr_se=#{rnrse}&phoneNumber=#{num.strip}&text=#{CGI.escape(options[:message])}&id="
      finalres = postit('https://www.google.com/voice/sms/send/',data,header)
      if finalres["ok"]
        puts "Message sent to #{num}"
      else
        puts "Error sending to #{num}"
      end
    }
  else
    newres.error!
  end
else
  res.error!
end

Usage

You can set default account name, password and send-to numbers at the top of the script. These can be overridden by command line options at runtime. The standard syntax is:

voicesms.rb -n +15552525543,+15554342221 -m “The message to send” -u GVoiceUsername:GVoicePassword

Parameters can be in any order. The -n parameter takes one or more phone numbers in international format (+XX country code at the beginning, +1 for US numbers), separated by commas (no spaces).

The -m option (message) is required and can’t be set by default in the script. Just use -m “and a quoted message” and it will handle the rest.

-u defines a username/password combination for Google Voice. It’s probably most convenient to set these at the top of the script and ignore this parameter, but the option is there.

-h will provide the help information.

Aliasing

The point of the script is to let me automate SMS messages from my system, so its primary invocation will be from other scripts. However, it will function just fine as a command line texting utility, in which case you’d probably want to alias the core functions in your .bash_profile. Set your default username and password in the script, and maybe a default destination number. If you want to send to a different numbers, you might want to make several aliases which include different -n # parameters, one for each destination. The alias will look something like:

alias sms=”~/scripts/voicesms.rb -m”

Or, maybe:

alias smsjohn=”~/scripts/voicesms.rb -n +15554443333 -m”

Get it? Then you can just type smsjohn "And your message" to send the message straight to John. Whoever that is.

What it does

It’s pretty simple, just a series of POST and GET requests to the Google API. It uses Google’s client authentication to get an initial auth code. Then it uses the auth code to get an auth token. Then, it posts your information to the API with the proper headers (the auth token) to complete the call. All of your data is sent securely over SSL (https) connections.

Have fun!

Brett Terpstra

Brett is a writer and developer living in Minnesota, USA. You can follow him as ttscoff on Twitter, GitHub, and Mastodon. Keep up with this blog by subscribing in your favorite news reader.

This content is supported by readers like you.

Join the conversation