Message type selection stuff
[gc-dialer] / src / gv_backend.py
index 1561b60..3f28b29 100644 (file)
@@ -25,6 +25,7 @@ Resources
        http://posttopic.com/topic/google-voice-add-on-development
 """
 
+from __future__ import with_statement
 
 import os
 import re
@@ -123,6 +124,8 @@ class GVDialer(object):
                self._callbackNumber = ""
                self._callbackNumbers = {}
 
+       _forwardURL = "https://www.google.com/voice/mobile/phones"
+
        def is_authed(self, force = False):
                """
                Attempts to detect a current session
@@ -147,11 +150,7 @@ class GVDialer(object):
        _loginURL = "https://www.google.com/accounts/ServiceLoginAuth"
        _galxRe = re.compile(r"""<input.*?name="GALX".*?value="(.*?)".*?/>""", re.MULTILINE | re.DOTALL)
 
-       def login(self, username, password):
-               """
-               Attempt to login to GoogleVoice
-               @returns Whether login was successful or not
-               """
+       def _get_token(self):
                try:
                        tokenPage = self._browser.download(self._tokenURL)
                except urllib2.URLError, e:
@@ -163,7 +162,9 @@ class GVDialer(object):
                else:
                        galxToken = ""
                        _moduleLogger.debug("Could not grab GALX token")
+               return galxToken
 
+       def _login(self, username, password, token):
                loginPostData = urllib.urlencode({
                        'Email' : username,
                        'Passwd' : password,
@@ -171,7 +172,7 @@ class GVDialer(object):
                        "ltmpl": "mobile",
                        "btmpl": "mobile",
                        "PersistentCookie": "yes",
-                       "GALX": galxToken,
+                       "GALX": token,
                        "continue": self._forwardURL,
                })
 
@@ -180,12 +181,27 @@ class GVDialer(object):
                except urllib2.URLError, e:
                        _moduleLogger.exception("Translating error: %s" % str(e))
                        raise NetworkError("%s is not accesible" % self._loginURL)
+               return loginSuccessOrFailurePage
+
+       def login(self, username, password):
+               """
+               Attempt to login to GoogleVoice
+               @returns Whether login was successful or not
+               """
+               self.logout()
+               galxToken = self._get_token()
+               loginSuccessOrFailurePage = self._login(username, password, galxToken)
 
                try:
                        self._grab_account_info(loginSuccessOrFailurePage)
                except Exception, e:
-                       _moduleLogger.exception(str(e))
-                       return False
+                       # Retry in case the redirect failed
+                       # luckily is_authed does everything we need for a retry
+                       loggedIn = self.is_authed(True)
+                       if not loggedIn:
+                               _moduleLogger.exception(str(e))
+                               return False
+                       _moduleLogger.info("Redirection failed on initial login attempt, auto-corrected for this")
 
                self._browser.cookies.save()
                self._lastAuthed = time.time()
@@ -268,8 +284,6 @@ class GVDialer(object):
                        return {}
                return self._callbackNumbers
 
-       _setforwardURL = "https://www.google.com//voice/m/setphone"
-
        def set_callback_number(self, callbacknumber):
                """
                Set the number that GoogleVoice calls
@@ -357,6 +371,16 @@ class GVDialer(object):
        _voicemailURL = "https://www.google.com/voice/inbox/recent/voicemail/"
        _smsURL = "https://www.google.com/voice/inbox/recent/sms/"
 
+       @staticmethod
+       def _merge_messages(parsedMessages, json):
+               for message in parsedMessages:
+                       id = message["id"]
+                       jsonItem = json["messages"][id]
+                       message["isRead"] = jsonItem["isRead"]
+                       message["isSpam"] = jsonItem["isSpam"]
+                       message["isTrash"] = jsonItem["isTrash"]
+                       yield message
+
        def get_messages(self):
                try:
                        voicemailPage = self._browser.download(self._voicemailURL)
@@ -364,8 +388,10 @@ class GVDialer(object):
                        _moduleLogger.exception("Translating error: %s" % str(e))
                        raise NetworkError("%s is not accesible" % self._voicemailURL)
                voicemailHtml = self._grab_html(voicemailPage)
+               voicemailJson = self._grab_json(voicemailPage)
                parsedVoicemail = self._parse_voicemail(voicemailHtml)
-               decoratedVoicemails = self._decorate_voicemail(parsedVoicemail)
+               voicemails = self._merge_messages(parsedVoicemail, voicemailJson)
+               decoratedVoicemails = self._decorate_voicemail(voicemails)
 
                try:
                        smsPage = self._browser.download(self._smsURL)
@@ -373,8 +399,10 @@ class GVDialer(object):
                        _moduleLogger.exception("Translating error: %s" % str(e))
                        raise NetworkError("%s is not accesible" % self._smsURL)
                smsHtml = self._grab_html(smsPage)
+               smsJson = self._grab_json(smsPage)
                parsedSms = self._parse_sms(smsHtml)
-               decoratedSms = self._decorate_sms(parsedSms)
+               smss = self._merge_messages(parsedSms, smsJson)
+               decoratedSms = self._decorate_sms(smss)
 
                allMessages = itertools.chain(decoratedVoicemails, decoratedSms)
                return allMessages
@@ -415,7 +443,6 @@ class GVDialer(object):
        _tokenRe = re.compile(r"""<input.*?name="_rnr_se".*?value="(.*?)"\s*/>""")
        _accountNumRe = re.compile(r"""<b class="ms\d">(.{14})</b></div>""")
        _callbackRe = re.compile(r"""\s+(.*?):\s*(.*?)<br\s*/>\s*$""", re.M)
-       _forwardURL = "https://www.google.com/voice/mobile/phones"
 
        def _grab_account_info(self, page):
                tokenGroup = self._tokenRe.search(page)
@@ -504,6 +531,7 @@ class GVDialer(object):
                                "number": number,
                                "location": location,
                                "messageParts": messageParts,
+                               "type": "Voicemail",
                        }
 
        def _decorate_voicemail(self, parsedVoicemails):
@@ -565,6 +593,7 @@ class GVDialer(object):
                                "number": number,
                                "location": "",
                                "messageParts": messageParts,
+                               "type": "Texts",
                        }
 
        def _decorate_sms(self, parsedTexts):
@@ -665,7 +694,7 @@ def test_backend(username, password):
                print "Login?: ", backend.login(username, password)
        print "Authenticated: ", backend.is_authed()
 
-       #print "Token: ", backend._token
+       print "Token: ", backend._token
        #print "Account: ", backend.get_account_number()
        #print "Callback: ", backend.get_callback_number()
        #print "All Callback: ",
@@ -694,7 +723,84 @@ def test_backend(username, password):
        return backend
 
 
+_TEST_WEBPAGES = [
+       ("forward", GVDialer._forwardURL),
+       ("token", GVDialer._tokenURL),
+       ("login", GVDialer._loginURL),
+       ("contacts", GVDialer._contactsURL),
+
+       ("voicemail", GVDialer._voicemailURL),
+       ("sms", GVDialer._smsURL),
+
+       ("recent", GVDialer._recentCallsURL),
+       ("placed", GVDialer._placedCallsURL),
+       ("recieved", GVDialer._receivedCallsURL),
+       ("missed", GVDialer._missedCallsURL),
+]
+
+
+def grab_debug_info(username, password):
+       cookieFile = os.path.join(".", "raw_cookies.txt")
+       try:
+               os.remove(cookieFile)
+       except OSError:
+               pass
+
+       backend = GVDialer(cookieFile)
+       browser = backend._browser
+
+       # Get Pages
+       print "Grabbing pre-login pages"
+       for name, url in _TEST_WEBPAGES:
+               try:
+                       page = browser.download(url)
+               except StandardError, e:
+                       print e.message
+                       continue
+               print "\tWriting to file"
+               with open("not_loggedin_%s.txt" % name, "w") as f:
+                       f.write(page)
+
+       # Login
+       print "Attempting login"
+       galxToken = backend._get_token()
+       loginSuccessOrFailurePage = backend._login(username, password, galxToken)
+       with open("loggingin.txt", "w") as f:
+               print "\tWriting to file"
+               f.write(loginSuccessOrFailurePage)
+       try:
+               backend._grab_account_info(loginSuccessOrFailurePage)
+       except Exception:
+               # Retry in case the redirect failed
+               # luckily is_authed does everything we need for a retry
+               loggedIn = backend.is_authed(True)
+               if not loggedIn:
+                       raise
+
+       # Get Pages
+       print "Grabbing post-login pages"
+       for name, url in _TEST_WEBPAGES:
+               try:
+                       page = browser.download(url)
+               except StandardError, e:
+                       print e.message
+                       continue
+               print "\tWriting to file"
+               with open("loggedin_%s.txt" % name, "w") as f:
+                       f.write(page)
+
+       # Cookies
+       browser.cookies.save()
+       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
+               )
+
+
 if __name__ == "__main__":
        import sys
        logging.basicConfig(level=logging.DEBUG)
-       test_backend(sys.argv[1], sys.argv[2])
+       #test_backend(sys.argv[1], sys.argv[2])
+       grab_debug_info(sys.argv[1], sys.argv[2])