Backwards logic, whoops
[gc-dialer] / src / gv_backend.py
index 6f3e7f8..0d05d41 100644 (file)
@@ -1,3 +1,15 @@
+#!/usr/bin/python
+
+# DialCentral - Front end for Google's Grand Central service.
+# Copyright (C) 2008  Eric Warnke ericew AT gmail DOT com
+# 
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+# 
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # Lesser General Public License for more details.
 # 
@@ -20,20 +32,17 @@ import urllib
 import urllib2
 import time
 import warnings
+import traceback
 
 from xml.etree import ElementTree
 
 from browser_emu import MozillaEmulator
 
-import socket
-
 try:
        import simplejson
 except ImportError:
        simplejson = None
 
-socket.setdefaulttimeout(5)
-
 
 _TRUE_REGEX = re.compile("true")
 _FALSE_REGEX = re.compile("false")
@@ -50,7 +59,7 @@ if simplejson is None:
                return safe_eval(flattened)
 else:
        def parse_json(flattened):
-               return simplejson.loads(json)
+               return simplejson.loads(flattened)
 
 
 class GVDialer(object):
@@ -59,11 +68,6 @@ class GVDialer(object):
        the functions include login, setting up a callback number, and initalting a callback
        """
 
-       _contactsRe = re.compile(r"""<a href="/mobile/contacts/detail/(\d+)">(.*?)</a>""", re.S)
-       _contactsNextRe = re.compile(r""".*<a href="/mobile/contacts(\?page=\d+)">Next</a>""", re.S)
-       _contactDetailGroupRe = re.compile(r"""Group:\s*(\w*)""", re.S)
-       _contactDetailPhoneRe = re.compile(r"""(\w+):[0-9\-\(\) \t]*?<a href="/mobile/calls/click_to_call\?destno=(\d+).*?">call</a>""", re.S)
-
        _isNotLoginPageRe = re.compile(r"""I cannot access my account""")
        _tokenRe = re.compile(r"""<input.*?name="_rnr_se".*?value="(.*?)"\s*/>""")
        _accountNumRe = re.compile(r"""<b class="ms2">(.{14})</b></div>""")
@@ -71,7 +75,11 @@ class GVDialer(object):
        _validateRe = re.compile("^[0-9]{10,}$")
        _gvDialingStrRe = re.compile("This may take a few seconds", re.M)
 
-       _clicktocallURL = "https://www.google.com/voice/m/callsms"
+       _contactsRe = re.compile(r"""<a href="/voice/m/contact/(\d+)">(.*?)</a>""", re.S)
+       _contactsNextRe = re.compile(r""".*<a href="/voice/m/contacts(\?p=\d+)">Next.*?</a>""", re.S)
+       _contactDetailPhoneRe = re.compile(r"""<div.*?>([0-9\-\(\) \t]+?)<span.*?>\((\w+)\)</span>""", re.S)
+
+       _clicktocallURL = "https://www.google.com/voice/m/sendcall"
        _contactsURL = "https://www.google.com/voice/mobile/contacts"
        _contactDetailURL = "https://www.google.com/voice/mobile/contact"
 
@@ -116,12 +124,14 @@ class GVDialer(object):
                try:
                        inboxPage = self._browser.download(self._inboxURL)
                except urllib2.URLError, e:
+                       warnings.warn(traceback.format_exc())
                        raise RuntimeError("%s is not accesible" % self._inboxURL)
 
                self._browser.cookies.save()
                if self._isNotLoginPageRe.search(inboxPage) is not None:
                        return False
 
+               self._grab_account_info()
                self._lastAuthed = time.time()
                return True
 
@@ -130,8 +140,8 @@ class GVDialer(object):
                Attempt to login to grandcentral
                @returns Whether login was successful or not
                """
-               #if self.is_authed():
-               #       return True
+               if self.is_authed():
+                       return True
 
                loginPostData = urllib.urlencode({
                        'Email' : username,
@@ -142,10 +152,9 @@ class GVDialer(object):
                try:
                        loginSuccessOrFailurePage = self._browser.download(self._loginURL, loginPostData)
                except urllib2.URLError, e:
+                       warnings.warn(traceback.format_exc())
                        raise RuntimeError("%s is not accesible" % self._loginURL)
 
-               #self._grab_account_info(loginSuccessOrFailurePage)
-               self._grab_account_info()
                return self.is_authed()
 
        def logout(self):
@@ -168,19 +177,19 @@ class GVDialer(object):
                        # Strip leading 1 from 11 digit dialing
                        number = number[1:]
 
-               #try:
-               clickToCallData = urllib.urlencode({
-                       "number": number,
-                       "_rnr_se": self._token,
-                       #"call": "Call",
-               })
-               otherData = {
-                       'Referer': self._accountNumberURL,
-               }
-               callSuccessPage = self._browser.download(self._clicktocallURL, clickToCallData, otherData)
-               #except urllib2.URLError, e:
-               #       print e.message
-               #       raise RuntimeError("%s is not accesible" % self._clicktocallURL)
+               try:
+                       clickToCallData = urllib.urlencode({
+                               "number": number,
+                               "phone": self._callbackNumber,
+                               "_rnr_se": self._token,
+                       })
+                       otherData = {
+                               'Referer' : 'https://google.com/voice/m/callsms',
+                       }
+                       callSuccessPage = self._browser.download(self._clicktocallURL, clickToCallData, None, otherData)
+               except urllib2.URLError, e:
+                       warnings.warn(traceback.format_exc())
+                       raise RuntimeError("%s is not accesible" % self._clicktocallURL)
 
                if self._gvDialingStrRe.search(callSuccessPage) is None:
                        raise RuntimeError("Google Voice returned an error")
@@ -254,6 +263,7 @@ class GVDialer(object):
                try:
                        callbackSetPage = self._browser.download(self._setforwardURL, callbackPostData)
                except urllib2.URLError, e:
+                       warnings.warn(traceback.format_exc())
                        raise RuntimeError("%s is not accesible" % self._setforwardURL)
 
                self._browser.cookies.save()
@@ -277,6 +287,7 @@ class GVDialer(object):
                        try:
                                allRecentData = self._grab_json(url)
                        except urllib2.URLError, e:
+                               warnings.warn(traceback.format_exc())
                                raise RuntimeError("%s is not accesible" % self._clicktocallURL)
 
                        for recentCallData in allRecentData["messages"].itervalues():
@@ -318,6 +329,7 @@ class GVDialer(object):
                                try:
                                        contactsPage = self._browser.download(contactsPageUrl)
                                except urllib2.URLError, e:
+                                       warnings.warn(traceback.format_exc())
                                        raise RuntimeError("%s is not accesible" % self._clicktocallURL)
                                for contact_match in self._contactsRe.finditer(contactsPage):
                                        contactId = contact_match.group(1)
@@ -341,11 +353,12 @@ class GVDialer(object):
                try:
                        detailPage = self._browser.download(self._contactDetailURL + '/' + contactId)
                except urllib2.URLError, e:
+                       warnings.warn(traceback.format_exc())
                        raise RuntimeError("%s is not accesible" % self._clicktocallURL)
 
                for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
-                       phoneType = detail_match.group(1)
-                       phoneNumber = detail_match.group(2)
+                       phoneNumber = detail_match.group(1)
+                       phoneType = detail_match.group(2)
                        yield (phoneType, phoneNumber)
 
        def _grab_json(self, url):
@@ -356,22 +369,25 @@ class GVDialer(object):
                jsonTree = parse_json(flatJson)
                return jsonTree
 
-       def _grab_account_info(self, loginPage = None):
-               if loginPage is None:
+       def _grab_account_info(self, accountNumberPage = None):
+               if accountNumberPage is None:
                        accountNumberPage = self._browser.download(self._accountNumberURL)
-               else:
-                       accountNumberPage = loginPage
+
                tokenGroup = self._tokenRe.search(accountNumberPage)
-               if tokenGroup is not None:
-                       self._token = tokenGroup.group(1)
+               if tokenGroup is None:
+                       raise RuntimeError("Could not extract authentication token from GrandCentral")
+               self._token = tokenGroup.group(1)
+
                anGroup = self._accountNumRe.search(accountNumberPage)
-               if anGroup is not None:
-                       self._accountNum = anGroup.group(1)
+               if atGroup is None:
+                       raise RuntimeError("Could not extract account number from GrandCentral")
+               self._accountNum = anGroup.group(1)
 
                callbackPage = self._browser.download(self._forwardURL)
                self._callbackNumbers = {}
                for match in self._callbackRe.finditer(callbackPage):
                        self._callbackNumbers[match.group(2)] = match.group(1)
+
                if len(self._callbackNumber) == 0:
                        self.set_sane_callback()
 
@@ -385,9 +401,13 @@ def test_backend(username, password):
        print "Token: ", backend._token
        print "Account: ", backend.get_account_number()
        print "Callback: ", backend.get_callback_number()
-       print "All Callback: ",
-       pprint.pprint(backend.get_callback_numbers())
-       print "Recent: ",
-       pprint.pprint(list(backend.get_recent()))
+       # print "All Callback: ",
+       # pprint.pprint(backend.get_callback_numbers())
+       # print "Recent: ",
+       # pprint.pprint(list(backend.get_recent()))
+       # print "Contacts: ",
+       # for contact in backend.get_contacts():
+       #       print contact
+       #       pprint.pprint(list(backend.get_contact_details(contact[0])))
 
        return backend