Jacks_Depression

Jacks_Soft Echo

Posted: 2009.09.04 16:44

Problem: If my Skype phone number is included in Google Voice and I am not online when the call is made. Skype goes right to voice mail, as does Google Voice.

Solution: Remove Skype from Google Voice when you are offline.

But of course, it sounds so easy when you put it like that. In fact, I thought I would be able to do this in a single day. Here I am 5 days later, finally writing these words. The biggest problem I cam across though, was with a python library. But thats for another post.

Considering Google Voice is still beta, you can consider this a hack. This is probably not the best way to go about doing this but it has been working for me for 12 hours already.

So here is the script....

import httplib, httplib2, urllib, re
from xml.etree import ElementTree
from StringIO import StringIO
import simplejson

user = 'example@gmail.com'
passwd = 'example'
source = 'gvoice-skype script'
account_type = 'GOOGLE'
skype_uid = 'example'
phone_name = 'Skype'

post_content = 'application/x-www-form-urlencoded'

skype_url = 'http://mystatus.skype.com/%s'
auth_url = 'https://www.google.com/voice/account/signin?auth=%s'
login_url = 'https://www.google.com/accounts/ClientLogin'
phones_url = 'https://www.google.com/voice/settings/tab/phones'
forwarding_url = 'https://www.google.com/voice/settings/editDefaultForwarding'

OFFLINE = 0
ONLINE = 1
OTHER = 2

statusHash = {
    -305107275330832706:ONLINE,
    -2234680221637025580:OFFLINE,
    -2821272954437439161:OTHER, #away
    4330642765452267998:OTHER, #not available
    1274729031316133580:OTHER, #do not disturbe
    7654804797299624632:OTHER, #skype me!
    }

webConnection = httplib2.Http()
webConnection.follow_redirects = False

gsession = re.compile('(?P<key>S)=(?P<val>grandcentral=[A-Za-z0-9-_]*)')
gvtoken = re.compile('(?P<key>gv)=(?P<val>[A-Za-z0-9-_]*)')
sidtoken = re.compile('(?P<key>SID)=(?P<val>[A-Za-z0-9-_]*)')
authtoken = re.compile('(?P<key>Auth)=(?P<val>[A-Za-z0-9-_]*)')


def main():
    #testRequest()
    sstatus = skypeOnlineStatus()
   
    tokens = mainAuth()
    combineTokens(tokens, reAuth(tokens))
   
    phoneid, gstatus = getPhoneId(phone_name, tokens)
    print('Phone id: %s, Enabled: %s' % (phoneid, gstatus))
   
    # If sykpe is doing something else, just set it to online
    simpleStatus = sstatus
    if simpleStatus == OTHER:
        simpleStatus = ONLINE
   
    # Only set if needed
    #if gstatus != simpleStatus:
    setPhone(phoneid, simpleStatus, tokens)
    #else:
        #print('No change needed')

def skypeOnlineStatus():
    response, body = webConnection.request(skype_url % skype_uid)
    imgHash = hash(body)
    if imgHash in statusHash:
        return statusHash[imgHash]
    else:
        print('Unknown skype hash')
        return OTHER

def mainAuth():
    """Parimary auth, expecting 'Auth' and 'SID' tokens"""
    loginStr = urllib.urlencode({'Passwd':passwd, 'source':source, 'Email':user, 'accountType':account_type, 'service':'grandcentral'})
    response, body = webConnection.request(login_url, 'POST', loginStr, {'Content-Type':post_content, 'user-agent':source})
    tokens = cookieMonster(body)
    if 'set-cookie' in response:
        allCookies = response['set-cookie']
    return tokens

def reAuth(tokens):
    """Another auth, expecting 'gv' and 'grandcentral' tokens"""
    morecookies = {}
    if 'Auth' in tokens:
        response, body = webConnection.request(auth_url % tokens['Auth'], 'GET', None, {'Cookie':cookieJar(tokens), 'user-agent':source})
        if 'set-cookie' in response:
            morecookies = cookieMonster(response['set-cookie'])
    return morecookies


def getPhoneId(phoneName, tokens):
    """Find the id of the phone you wanna toggle. idk if it will ever change, but just in case."""
    print('Cookie: %s' % cookieJar(tokens))
    response, body = webConnection.request(phones_url,'GET', None, {'Cookie':cookieJar(tokens), 'user-agent':source})
   
    if response.status == 200:
        resultingXml = ElementTree.fromstring(body)
        phonedata = resultingXml.find('./json')
        if phonedata is not None:
            phoneObj = simplejson.load(StringIO(phonedata.text))
            for phone in phoneObj['phones'].itervalues():
                if phone['name'] == phoneName:
                    return (phone['id'], phone['active'])


def setPhone(id, status, tokens):
    """Set the phone status"""
    #Not sure what _rnr_se is but it will 500 without it
    postData = 'phoneId=%s&enabled=%s' % (id, status)
    postData += '&_rnr_se=bVaWimKRDR3b4AaLlykfAW2zqsk%3D'
    response, body = webConnection.request(forwarding_url, 'POST', postData, {'Cookie':cookieJar(tokens),'Content-type':post_content, 'user-agent':source})
    if response.status == 200:
        print('Phone set to: %s' % status)
    else:
        print('Set failed')

def cookieMonster(cookies):
    """Parse select key, value pairs out of given string using re"""
    cookieMatches = {}
    if cookies:
        searches = (gsession, gvtoken, sidtoken, authtoken)
        for search in searches:
            reResult = search.search(cookies)
            if reResult:
                key = reResult.group('key')
                val = reResult.group('val')
                cookieMatches[key] = val
    else:
        print('No cookies?')
    return cookieMatches


def cookieJar(tokens):
    """Convert token dict into cookie format"""
    jar = ''
    for token in tokens:
        jar += token + '=' + tokens[token] + '; '
    return jar

def combineTokens(origional, addition):
    """Convience function to combine dicts"""
    while addition:
        addKey, addVal = addition.popitem()
        if addKey in origional:
            if origional[addKey] != addVal:
                origional[addKey] = addVal
        else:
            origional[addKey] = addVal


if __name__ == "__main__":
    main()

Personally, I run it as a cron at 10 minute intervals on one of my servers. I don't know if anyone else will find this useful but here it is.