google-voice-call.py

2009-04-30 17:29

Simple python script that automates the Google Voice web interface to make outgoing calls. Requires httplib2, simplejson, and BeautifulSoup

This script is not tested on Windows but should work, it looks for a file in the users home directory named .gvoice_auth to determine the credentials to use and the default number to use for the 'from' leg of the call. The file is a JSON encoded object with the following attributes:

user
Your username / email address that you use to login to Google Voice
passwd
The password you use to login to Google Voice
default_from : optional
The number to use for your leg of the call if you don't specify a different one.

For example:

{'user':'me@gmail.com',
 'passwd':'lamepasswd',
 'default_from':'+15855551234'}

Then, simply run the script:

$ python google-voice-call.py 5855551235
Call request returned:  {"ok":true,"data":{"code":0}}

Here's the code, though you probably want to grab google-voice-call.py directly, cut and paste can be annoying.

#!/usr/bin/env python
'''
    A ugly script to pretend that Google Voice has an API for calling out.
'''

import sys, os, re
import urllib, httplib2
from os import path
from simplejson import load
from BeautifulSoup import BeautifulSoup

def main():
    if not path.exists(path.join(
                        path.expanduser('~'),
                        '.gvoice_auth')):
        return """
You need to create a config file in your home directory\
called '.gvoice_auth' with your Google Account's credentials.\
"""
    else:
        creds = load(open(path.join(path.expanduser('~'),'.gvoice_auth')))

    if len(sys.argv) == 2:
        fromnum = creds['default_from']
        tonum = sys.argv[1]
    elif len(sys.argv) == 3:
        fromnum = sys.argv[2]
        tonum = sys.argv[1]
    else:
        return "Invalid number of arguments\nUsage: %s <number> from_number]" % sys.argv[0]

    auth_tok = authenticate(creds['user'], creds['passwd'], 'grandcentral')


    r = call(tonum, fromnum, auth_tok)

    print "Call request returned: ", r


def call(number, fromnum, auth):
    data = {
        'outgoingNumber': number,
        'forwardingNumber': fromnum,
        'remember': 0,
        }

    main_uri = "https://www.google.com/voice"
    call_uri = "https://www.google.com/voice/call/connect/"
    headers = { 'Content-Type': 'application/x-www-form-urlencoded',
                'Cookie': 'gv=%s' % auth }

    h = httplib2.Http()

    response, content = h.request(main_uri, 'GET', headers=headers)
    if not response['status'].startswith('2'):
        raise Exception(response['status'] + ': ' + content)

    soup = BeautifulSoup(content)
    _rnr_se = soup.find('input', attrs={'name':'_rnr_se'})

    data['_rnr_se'] = _rnr_se['value']

    response, content = h.request(call_uri, 'POST', body=urllib.urlencode(data),
                                  headers=headers)
    if response['status'].startswith('2'):
        return content
    else:
        raise Exception(response['status'] + ': ' + content)


def authenticate(user, passwd, service):
    '''
        Basic perform ClientLogin method of authenticating a Google Account.

        Returns the Auth=... bit required in the Authorization header.
    '''
    h = httplib2.Http()
    uri = 'https://www.google.com/accounts/ClientLogin'

    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    request = urllib.urlencode({
        'Email': user,
        'Passwd': passwd,
        'service': service,
        })

    response, content = h.request(uri, 'POST', body=request, headers=headers)
    if response['status'] == '200':
        return re.search('Auth=(\S*)', content).group(1).strip()
    else:
        raise Exception(response['status'] + ': ' + content)

if __name__=='__main__':
    sys.exit(main())