Starting to play with TheOneRing inside of empathy :)
[theonering] / src / gvoice / backend.py
index 18eb7f3..b2b01a3 100755 (executable)
@@ -79,7 +79,7 @@ class GVoiceBackend(object):
 
                # Suprisingly, moving all of these from class to self sped up startup time
 
-               self._validateRe = re.compile("^[0-9]{10,}$")
+               self._validateRe = re.compile("^\+?[0-9]{10,}$")
 
                self._loginURL = "https://www.google.com/accounts/ServiceLoginAuth"
 
@@ -98,7 +98,9 @@ class GVoiceBackend(object):
                self._downloadVoicemailURL = SECURE_URL_BASE + "media/send_voicemail/"
 
                self._XML_SEARCH_URL = SECURE_URL_BASE + "inbox/search/"
-               self._XML_ACCOUNT_URL = SECURE_URL_BASE + "inbox/contacts/"
+               self._XML_ACCOUNT_URL = SECURE_URL_BASE + "contacts/"
+               # HACK really this redirects to the main pge and we are grabbing some javascript
+               self._XML_CONTACTS_URL = "http://www.google.com/voice/inbox/search/contact"
                self._XML_RECENT_URL = SECURE_URL_BASE + "inbox/recent/"
 
                self.XML_FEEDS = (
@@ -117,18 +119,12 @@ class GVoiceBackend(object):
                self._XML_RECEIVED_URL = SECURE_URL_BASE + "inbox/recent/received/"
                self._XML_MISSED_URL = SECURE_URL_BASE + "inbox/recent/missed/"
 
-               self._contactsURL = SECURE_MOBILE_URL_BASE + "contacts"
-               self._contactDetailURL = SECURE_MOBILE_URL_BASE + "contact"
-
                self._galxRe = re.compile(r"""<input.*?name="GALX".*?value="(.*?)".*?/>""", re.MULTILINE | re.DOTALL)
                self._tokenRe = re.compile(r"""<input.*?name="_rnr_se".*?value="(.*?)"\s*/>""")
                self._accountNumRe = re.compile(r"""<b class="ms\d">(.{14})</b></div>""")
                self._callbackRe = re.compile(r"""\s+(.*?):\s*(.*?)<br\s*/>\s*$""", re.M)
 
-               self._contactsRe = re.compile(r"""<a href="/voice/m/contact/(\d+)">(.*?)</a>""", re.S)
-               self._contactsNextRe = re.compile(r""".*<a href="/voice/m/contacts(\?p=\d+)">Next.*?</a>""", re.S)
-               self._contactDetailPhoneRe = re.compile(r"""<div.*?>([0-9+\-\(\) \t]+?)<span.*?>\((\w+)\)</span>""", re.S)
-
+               self._contactsBodyRe = re.compile(r"""gcData\s*=\s*({.*?});""", re.MULTILINE | re.DOTALL)
                self._seperateVoicemailsRegex = re.compile(r"""^\s*<div id="(\w+)"\s* class=".*?gc-message.*?">""", re.MULTILINE | re.DOTALL)
                self._exactVoicemailTimeRegex = re.compile(r"""<span class="gc-message-time">(.*?)</span>""", re.MULTILINE)
                self._relativeVoicemailTimeRegex = re.compile(r"""<span class="gc-message-relative">(.*?)</span>""", re.MULTILINE)
@@ -142,6 +138,12 @@ class GVoiceBackend(object):
                self._smsTimeRegex = re.compile(r"""<span class="gc-message-sms-time">(.*?)</span>""", re.MULTILINE | re.DOTALL)
                self._smsTextRegex = re.compile(r"""<span class="gc-message-sms-text">(.*?)</span>""", re.MULTILINE | re.DOTALL)
 
+       def is_quick_login_possible(self):
+               """
+               @returns True then is_authed might be enough to login, else full login is required
+               """
+               return self._loadedFromCookies or 0.0 < self._lastAuthed
+
        def is_authed(self, force = False):
                """
                Attempts to detect a current session
@@ -246,15 +248,18 @@ class GVoiceBackend(object):
                subscriberNumber = None
                phoneType = guess_phone_type(self._callbackNumber) # @todo Fix this hack
 
-               page = self._get_page_with_token(
-                       self._callUrl,
-                       {
+               callData = {
                                'outgoingNumber': outgoingNumber,
                                'forwardingNumber': self._callbackNumber,
                                'subscriberNumber': subscriberNumber or 'undefined',
-                               'phoneType': phoneType,
-                               'remember': '1'
-                       },
+                               'phoneType': str(phoneType),
+                               'remember': '1',
+               }
+               _moduleLogger.info("%r" % callData)
+
+               page = self._get_page_with_token(
+                       self._callUrl,
+                       callData,
                )
                self._parse_with_validation(page)
                return True
@@ -376,30 +381,15 @@ class GVoiceBackend(object):
                """
                @returns Iterable of (contact id, contact name)
                """
-               contactsPagesUrls = [self._contactsURL]
-               for contactsPageUrl in contactsPagesUrls:
-                       contactsPage = self._get_page(contactsPageUrl)
-                       for contact_match in self._contactsRe.finditer(contactsPage):
-                               contactId = contact_match.group(1)
-                               contactName = saxutils.unescape(contact_match.group(2))
-                               contact = contactId, contactName
-                               yield contact
-
-                       next_match = self._contactsNextRe.match(contactsPage)
-                       if next_match is not None:
-                               newContactsPageUrl = self._contactsURL + next_match.group(1)
-                               contactsPagesUrls.append(newContactsPageUrl)
-
-       def get_contact_details(self, contactId):
-               """
-               @returns Iterable of (Phone Type, Phone Number)
-               """
-               detailPage = self._get_page(self._contactDetailURL + '/' + contactId)
-
-               for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
-                       phoneNumber = detail_match.group(1)
-                       phoneType = saxutils.unescape(detail_match.group(2))
-                       yield (phoneType, phoneNumber)
+               page = self._get_page(self._XML_CONTACTS_URL)
+               contactsBody = self._contactsBodyRe.search(page)
+               if contactsBody is None:
+                       raise RuntimeError("Could not extract contact information")
+               accountData = _fake_parse_json(contactsBody.group(1))
+               for contactId, contactDetails in accountData["contacts"].iteritems():
+                       # A zero contact id is the catch all for unknown contacts
+                       if contactId != "0":
+                               yield contactId, contactDetails
 
        def get_messages(self):
                voicemailPage = self._get_page(self._XML_VOICEMAIL_URL)
@@ -607,9 +597,9 @@ class GVoiceBackend(object):
                return page
 
        def _parse_with_validation(self, page):
-               json, html = extract_payload(page)
+               json = parse_json(page)
                validate_response(json)
-               return json, html
+               return json
 
 
 def itergroup(iterator, count, padValue = None):
@@ -692,9 +682,9 @@ def validate_response(response):
 
 def guess_phone_type(number):
        if number.startswith("747") or number.startswith("1747"):
-               return GVDialer.PHONE_TYPE_GIZMO
+               return GVoiceBackend.PHONE_TYPE_GIZMO
        else:
-               return GVDialer.PHONE_TYPE_MOBILE
+               return GVoiceBackend.PHONE_TYPE_MOBILE
 
 
 def set_sane_callback(backend):
@@ -790,7 +780,7 @@ def test_backend(username, password):
        if not backend.is_authed():
                print "Login?: ", backend.login(username, password)
        print "Authenticated: ", backend.is_authed()
-       print "Is Dnd: ", backend.is_dnd()
+       #print "Is Dnd: ", backend.is_dnd()
        #print "Setting Dnd", backend.set_dnd(True)
        #print "Is Dnd: ", backend.is_dnd()
        #print "Setting Dnd", backend.set_dnd(False)
@@ -800,7 +790,7 @@ def test_backend(username, password):
        #print "Account: ", backend.get_account_number()
        #print "Callback: ", backend.get_callback_number()
        #print "All Callback: ",
-       #import pprint
+       import pprint
        #pprint.pprint(backend.get_callback_numbers())
 
        #print "Recent: "
@@ -810,10 +800,9 @@ def test_backend(username, password):
        #       pprint.pprint(decorate_recent(data))
        #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])))
+       print "Contacts: ",
+       for contact in backend.get_contacts():
+               pprint.pprint(contact)
 
        #print "Messages: ",
        #for message in backend.get_messages():
@@ -839,9 +828,9 @@ def grab_debug_info(username, password):
                ("token", backend._tokenURL),
                ("login", backend._loginURL),
                ("isdnd", backend._isDndURL),
-               ("contacts", backend._contactsURL),
-
                ("account", backend._XML_ACCOUNT_URL),
+               ("contacts", backend._XML_CONTACTS_URL),
+
                ("voicemail", backend._XML_VOICEMAIL_URL),
                ("sms", backend._XML_SMS_URL),
 
@@ -885,19 +874,19 @@ def grab_debug_info(username, password):
                try:
                        page = browser.download(url)
                except StandardError, e:
-                       print e.message
+                       print str(e)
                        continue
                print "\tWriting to file"
                with open("loggedin_%s.txt" % name, "w") as f:
                        f.write(page)
 
        # Cookies
-       browser.cookies.save()
+       browser.save_cookies()
        print "\tWriting cookies to file"
        with open("cookies.txt", "w") as f:
                f.writelines(
                        "%s: %s\n" % (c.name, c.value)
-                       for c in browser.cookies
+                       for c in browser._cookies
                )