* Explicitly set the python version
[gc-dialer] / gc_dialer / gcbackend.py
index 7e15ec8..29a6721 100644 (file)
@@ -10,6 +10,7 @@ Copyright 2008 GPLv2
 import os
 import re
 import urllib
+import time
 
 from browser_emu import MozillaEmulator
 
@@ -20,7 +21,7 @@ class GCDialer(object):
        the functions include login, setting up a callback number, and initalting a callback
        """
 
-       _wgetOKstrRe    = re.compile("This may take a few seconds", re.M)       # string from Grandcentral.com on successful dial
+       _gcDialingStrRe = re.compile("This may take a few seconds", re.M)       # string from Grandcentral.com on successful dial
        _validateRe     = re.compile("^[0-9]{10,}$")
        _accessTokenRe  = re.compile(r"""<input type="hidden" name="a_t" [^>]*value="(.*)"/>""")
        _isLoginPageRe  = re.compile(r"""<form method="post" action="https://www.grandcentral.com/mobile/account/login">""")
@@ -36,9 +37,8 @@ class GCDialer(object):
 
        def __init__(self, cookieFile = None):
                # Important items in this function are the setup of the browser emulation and cookie file
-
                self._msg = ""
-               if cookieFile == None:
+               if cookieFile is None:
                        cookieFile = os.path.join(os.path.expanduser("~"), ".gc_dialer_cookies.txt")
                self._browser = MozillaEmulator(None, 0)
                self._browser.cookies.filename = cookieFile
@@ -49,10 +49,11 @@ class GCDialer(object):
                self._lastData = ""
                self._accessToken = None
                self._accountNum = None
+               self._callbackNumbers = {}
+               self._lastAuthed = 0.0
 
        def grabToken(self, data):
-               # Pull the magic cookie from the datastream
-
+               "Pull the magic cookie from the datastream"
                atGroup = GCDialer._accessTokenRe.search(data)
                try:
                        self._accessToken = atGroup.group(1)
@@ -65,33 +66,51 @@ class GCDialer(object):
                except:
                        pass
 
+               self._callbackNumbers = {}
+               try:
+                       for match in GCDialer._callbackRe.finditer(data):
+                               self._callbackNumbers[match.group(1)] = match.group(2)
+               except:
+                       pass
+
        def getAccountNumber(self):
                return self._accountNum
 
-       def isAuthed(self):
+       def isAuthed(self, force = False):
                """
                Attempts to detect a current session and pull the
-               auth token ( a_t ) from the page
+               auth token ( a_t ) from the page.  Once logged in
+               try not to reauth more than once a minute.
                """
+       
 
-               self._lastData = self._browser.download(GCDialer._forwardselectURL)
-               self._browser.cookies.save()
-               if GCDialer._isLoginPageRe.search(self._lastData) is None:
-                       self.grabToken(self._lastData)
+               if time.time() - self._lastAuthed < 60 and not force:
                        return True
+
+               try:    
+                       self._lastData = self._browser.download(GCDialer._forwardselectURL)
+                       self._browser.cookies.save()
+                       if GCDialer._isLoginPageRe.search(self._lastData) is None:
+                               self.grabToken(self._lastData)
+                               self._lastAuthed = time.time()
+                               return True
+               except:
+                       pass
                return False
 
        def login(self, username, password):
                """
                Attempt to login to grandcentral
                """
-
-               if self.isAuthed():
-                       return
-
-               loginPostData = urllib.urlencode( {'username' : username , 'password' : password } )
-               self._lastData = self._browser.download(GCDialer._loginURL, loginPostData)
-               return self.isAuthed()
+               try:
+                       if self.isAuthed():
+                               return
+                       loginPostData = urllib.urlencode( {'username' : username , 'password' : password } )
+                       self._lastData = self._browser.download(GCDialer._loginURL, loginPostData)
+                       return self.isAuthed()
+               except:
+                       pass
+               return False
 
        def setSaneCallback(self):
                """
@@ -101,46 +120,51 @@ class GCDialer(object):
                3) anything with computer in the name
                4) the first value
                """
-
+               print "setSaneCallback"
                numbers = self.getCallbackNumbers()
 
-               for k, v in numbers.iteritems():
-                       if not re.compile(r"""1747""").match(k) is None:
-                               self.setCallbackNumber(k)
+               for number, description in numbers.iteritems():
+                       if not re.compile(r"""1747""").match(number) is None:
+                               self.setCallbackNumber(number)
                                return
 
-               for k, v in numbers.iteritems():
-                       if not re.compile(r"""gizmo""", re.I).search(v) is None:
-                               self.setCallbackNumber(k)
+               for number, description in numbers.iteritems():
+                       if not re.compile(r"""gizmo""", re.I).search(description) is None:
+                               self.setCallbackNumber(number)
                                return
 
-               for k, v in numbers.iteritems():
-                       if not re.compile(r"""computer""", re.I).search(v) is None:
-                               self.setCallbackNumber(k)
+               for number, description in numbers.iteritems():
+                       if not re.compile(r"""computer""", re.I).search(description) is None:
+                               self.setCallbackNumber(number)
                                return
 
-               for k, v in numbers.iteritems():
-                       self.setCallbackNumber(k)
+               for number, description in numbers.iteritems():
+                       self.setCallbackNumber(number)
                        return
 
        def getCallbackNumbers(self):
-               retval = {}
-
-               self._lastData = self._browser.download(GCDialer._forwardselectURL)
-               for match in GCDialer._callbackRe.finditer(self._lastData):
-                       retval[match.group(1)] = match.group(2)
+               """
+               @returns a dictionary mapping call back numbers to descriptions. These results
+               are cached for 30 minutes.
+               """
+               print "getCallbackNumbers"
+               if time.time() - self._lastAuthed < 1800 or self.isAuthed():
+                       return self._callbackNumbers
 
-               return retval
+               return {}
 
        def setCallbackNumber(self, callbacknumber):
                """
                set the number that grandcental calls
                this should be a proper 10 digit number
                """
-
-               callbackPostData = urllib.urlencode({'a_t' : self._accessToken, 'default_number' : callbacknumber })
-               self._lastData = self._browser.download(GCDialer._setforwardURL, callbackPostData)
-               self._browser.cookies.save()
+               print "setCallbackNumber %s" % (callbacknumber)
+               try:
+                       callbackPostData = urllib.urlencode({'a_t' : self._accessToken, 'default_number' : callbacknumber })
+                       self._lastData = self._browser.download(GCDialer._setforwardURL, callbackPostData)
+                       self._browser.cookies.save()
+               except:
+                       pass
 
        def getCallbackNumber(self):
                for c in self._browser.cookies:
@@ -148,7 +172,14 @@ class GCDialer(object):
                                return c.value
                return None
 
+       def clear_caches(self):
+               """
+               @todo Fill this in
+               """
+               pass
+
        def reset(self):
+               self._lastAuthed = 0.0
                self._browser.cookies.clear()
                self._browser.cookies.save()
 
@@ -156,13 +187,13 @@ class GCDialer(object):
                """
                Can this number be called ( syntax validation only )
                """
-
                return GCDialer._validateRe.match(number) is not None
 
        def dial(self, number):
                """
                This is the main function responsible for initating the callback
                """
+               self._msg = ""
 
                # If the number is not valid throw exception
                if self.validate(number) is False:
@@ -170,25 +201,33 @@ class GCDialer(object):
 
                # No point if we don't have the magic cookie
                if not self.isAuthed():
+                       self._msg = "Not authenticated"
                        return False
 
                # Strip leading 1 from 11 digit dialing
                if len(number) == 11 and number[0] == 1:
                        number = number[1:]
 
-               self._lastData = self._browser.download(
-                       GCDialer._clicktocallURL % (self._accessToken, number),
-                       None, {'Referer' : 'http://www.grandcentral.com/mobile/messages'} )
-
-               if GCDialer._wgetOKstrRe.search(self._lastData) != None:
-                       return True
-               else:
-                       return False
+               try:
+                       self._lastData = self._browser.download(
+                               GCDialer._clicktocallURL % (self._accessToken, number),
+                               None, {'Referer' : 'http://www.grandcentral.com/mobile/messages'} )
+
+                       if GCDialer._gcDialingStrRe.search(self._lastData) is not None:
+                               return True
+                       else:
+                               self._msg = "Grand Central returned an error"
+                               return False
+               except:
+                       pass
+       
+               self._msg = "Unknown Error"
+               return False
 
        def get_recent(self):
-               retval = []
-               self._lastData = self._browser.download(GCDialer._inboxallURL)
-               for match in self._inboxRe.finditer(self._lastData):
-                       #print "type: %s date: %s person: %s number: %s" % (match.group(1), match.group(2), match.group(3), match.group(4))
-                       retval.append([match.group(4), "%s on %s from/to %s - %s" % (match.group(1).capitalize(), match.group(2), match.group(3), match.group(4))])
-               return retval
+               try:
+                       self._lastData = self._browser.download(GCDialer._inboxallURL)
+                       for match in self._inboxRe.finditer(self._lastData):
+                               yield (match.group(4), "%s on %s from/to %s - %s" % (match.group(1).capitalize(), match.group(2), match.group(3), match.group(4)))
+               except:
+                       pass