Noting where I block to find them faster
[theonering] / src / gvoice / backend.py
index bf9aed1..9996e74 100755 (executable)
@@ -48,7 +48,7 @@ except ImportError:
 import browser_emu
 
 
-_moduleLogger = logging.getLogger("gvoice.backend")
+_moduleLogger = logging.getLogger(__name__)
 
 
 class NetworkError(RuntimeError):
@@ -235,6 +235,7 @@ class GVoiceBackend(object):
                Attempts to detect a current session
                @note Once logged in try not to reauth more than once a minute.
                @returns If authenticated
+               @blocks
                """
                isRecentledAuthed = (time.time() - self._lastAuthed) < 120
                isPreviouslyAuthed = self._token is not None
@@ -282,6 +283,7 @@ class GVoiceBackend(object):
                """
                Attempt to login to GoogleVoice
                @returns Whether login was successful or not
+               @blocks
                """
                self.logout()
                galxToken = self._get_token()
@@ -309,6 +311,9 @@ class GVoiceBackend(object):
                self._lastAuthed = 0.0
 
        def is_dnd(self):
+               """
+               @blocks
+               """
                isDndPage = self._get_page(self._isDndURL)
 
                dndGroup = self._isDndRe.search(isDndPage)
@@ -319,6 +324,9 @@ class GVoiceBackend(object):
                return isDnd
 
        def set_dnd(self, doNotDisturb):
+               """
+               @blocks
+               """
                dndPostData = {
                        "doNotDisturb": 1 if doNotDisturb else 0,
                }
@@ -328,6 +336,7 @@ class GVoiceBackend(object):
        def call(self, outgoingNumber):
                """
                This is the main function responsible for initating the callback
+               @blocks
                """
                outgoingNumber = self._send_validation(outgoingNumber)
                subscriberNumber = None
@@ -353,6 +362,7 @@ class GVoiceBackend(object):
                """
                Cancels a call matching outgoing and forwarding numbers (if given). 
                Will raise an error if no matching call is being placed
+               @blocks
                """
                page = self._get_page_with_token(
                        self._callCancelURL,
@@ -364,12 +374,19 @@ class GVoiceBackend(object):
                )
                self._parse_with_validation(page)
 
-       def send_sms(self, phoneNumber, message):
-               phoneNumber = self._send_validation(phoneNumber)
+       def send_sms(self, phoneNumbers, message):
+               """
+               @blocks
+               """
+               validatedPhoneNumbers = [
+                       self._send_validation(phoneNumber)
+                       for phoneNumber in phoneNumbers
+               ]
+               flattenedPhoneNumbers = ",".join(validatedPhoneNumbers)
                page = self._get_page_with_token(
                        self._sendSmsURL,
                        {
-                               'phoneNumber': phoneNumber,
+                               'phoneNumber': flattenedPhoneNumbers,
                                'text': message
                        },
                )
@@ -379,6 +396,7 @@ class GVoiceBackend(object):
                """
                Search your Google Voice Account history for calls, voicemails, and sms
                Returns ``Folder`` instance containting matching messages
+               @blocks
                """
                page = self._get_page(
                        self._XML_SEARCH_URL,
@@ -388,6 +406,9 @@ class GVoiceBackend(object):
                return json
 
        def get_feed(self, feed):
+               """
+               @blocks
+               """
                actualFeed = "_XML_%s_URL" % feed.upper()
                feedUrl = getattr(self, actualFeed)
 
@@ -402,7 +423,8 @@ class GVoiceBackend(object):
                which can either be a ``Message`` instance, or a SHA1 identifier. 
                Saves files to ``adir`` (defaults to current directory). 
                Message hashes can be found in ``self.voicemail().messages`` for example. 
-               Returns location of saved file.
+               @returns location of saved file.
+               @blocks
                """
                page = self._get_page(self._downloadVoicemailURL, {"id": messageId})
                fn = os.path.join(adir, '%s.mp3' % messageId)
@@ -449,6 +471,7 @@ class GVoiceBackend(object):
        def get_recent(self):
                """
                @returns Iterable of (personsName, phoneNumber, exact date, relative date, action)
+               @blocks
                """
                for action, url in (
                        ("Received", self._XML_RECEIVED_URL),
@@ -466,6 +489,7 @@ class GVoiceBackend(object):
        def get_contacts(self):
                """
                @returns Iterable of (contact id, contact name)
+               @blocks
                """
                page = self._get_page(self._XML_CONTACTS_URL)
                contactsBody = self._contactsBodyRe.search(page)
@@ -477,23 +501,32 @@ class GVoiceBackend(object):
                        if contactId != "0":
                                yield contactId, contactDetails
 
-       def get_conversations(self):
+       def get_voicemails(self):
+               """
+               @blocks
+               """
                voicemailPage = self._get_page(self._XML_VOICEMAIL_URL)
                voicemailHtml = self._grab_html(voicemailPage)
                voicemailJson = self._grab_json(voicemailPage)
                parsedVoicemail = self._parse_voicemail(voicemailHtml)
                voicemails = self._merge_conversation_sources(parsedVoicemail, voicemailJson)
+               return voicemails
 
+       def get_texts(self):
+               """
+               @blocks
+               """
                smsPage = self._get_page(self._XML_SMS_URL)
                smsHtml = self._grab_html(smsPage)
                smsJson = self._grab_json(smsPage)
                parsedSms = self._parse_sms(smsHtml)
                smss = self._merge_conversation_sources(parsedSms, smsJson)
-
-               allConversations = itertools.chain(voicemails, smss)
-               return allConversations
+               return smss
 
        def mark_message(self, messageId, asRead):
+               """
+               @blocks
+               """
                postData = {
                        "read": 1 if asRead else 0,
                        "id": messageId,
@@ -502,6 +535,9 @@ class GVoiceBackend(object):
                markPage = self._get_page(self._markAsReadURL, postData)
 
        def archive_message(self, messageId):
+               """
+               @blocks
+               """
                postData = {
                        "id": messageId,
                }
@@ -546,10 +582,6 @@ class GVoiceBackend(object):
                        raise ValueError('Number is not valid: "%s"' % number)
                elif not self.is_authed():
                        raise RuntimeError("Not Authenticated")
-
-               if len(number) == 11 and number[0] == 1:
-                       # Strip leading 1 from 11 digit dialing
-                       number = number[1:]
                return number
 
        def _parse_history(self, historyHtml):
@@ -557,7 +589,7 @@ class GVoiceBackend(object):
                for messageId, messageHtml in itergroup(splitVoicemail[1:], 2):
                        exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
                        exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
-                       exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p")
+                       exactTime = google_strptime(exactTime)
                        relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
                        relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
                        locationGroup = self._voicemailLocationRegex.search(messageHtml)
@@ -605,7 +637,7 @@ class GVoiceBackend(object):
 
                        exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
                        exactTimeText = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
-                       conv.time = datetime.datetime.strptime(exactTimeText, "%m/%d/%y %I:%M %p")
+                       conv.time = google_strptime(exactTimeText)
                        relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
                        conv.relTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
                        locationGroup = self._voicemailLocationRegex.search(messageHtml)
@@ -655,7 +687,7 @@ class GVoiceBackend(object):
 
                        exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
                        exactTimeText = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
-                       conv.time = datetime.datetime.strptime(exactTimeText, "%m/%d/%y %I:%M %p")
+                       conv.time = google_strptime(exactTimeText)
                        relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
                        conv.relTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
                        conv.location = ""
@@ -722,6 +754,19 @@ class GVoiceBackend(object):
                return json
 
 
+def google_strptime(time):
+       """
+       Hack: Google always returns the time in the same locale.  Sadly if the
+       local system's locale is different, there isn't a way to perfectly handle
+       the time.  So instead we handle implement some time formatting
+       """
+       abbrevTime = time[:-3]
+       parsedTime = datetime.datetime.strptime(abbrevTime, "%m/%d/%y %I:%M")
+       if time[-2] == "PN":
+               parsedTime += datetime.timedelta(hours=12)
+       return parsedTime
+
+
 def itergroup(iterator, count, padValue = None):
        """
        Iterate in groups of 'count' values. If there
@@ -801,13 +846,13 @@ def validate_response(response):
 
 
 def guess_phone_type(number):
-       if number.startswith("747") or number.startswith("1747"):
+       if number.startswith("747") or number.startswith("1747") or number.startswith("+1747"):
                return GVoiceBackend.PHONE_TYPE_GIZMO
        else:
                return GVoiceBackend.PHONE_TYPE_MOBILE
 
 
-def set_sane_callback(backend):
+def get_sane_callback(backend):
        """
        Try to set a sane default callback number on these preferences
        1) 1747 numbers ( Gizmo )
@@ -818,7 +863,9 @@ def set_sane_callback(backend):
        numbers = backend.get_callback_numbers()
 
        priorityOrderedCriteria = [
+               ("\+1747", None),
                ("1747", None),
+               ("747", None),
                (None, "gizmo"),
                (None, "computer"),
                (None, "sip"),
@@ -826,13 +873,31 @@ def set_sane_callback(backend):
        ]
 
        for numberCriteria, descriptionCriteria in priorityOrderedCriteria:
+               numberMatcher = None
+               descriptionMatcher = None
+               if numberCriteria is not None:
+                       numberMatcher = re.compile(numberCriteria)
+               elif descriptionCriteria is not None:
+                       descriptionMatcher = re.compile(descriptionCriteria, re.I)
+
                for number, description in numbers.iteritems():
-                       if numberCriteria is not None and re.compile(numberCriteria).match(number) is None:
+                       if numberMatcher is not None and numberMatcher.match(number) is None:
                                continue
-                       if descriptionCriteria is not None and re.compile(descriptionCriteria).match(description) is None:
+                       if descriptionMatcher is not None and descriptionMatcher.match(description) is None:
                                continue
-                       backend.set_callback_number(number)
-                       return
+                       return number
+
+
+def set_sane_callback(backend):
+       """
+       Try to set a sane default callback number on these preferences
+       1) 1747 numbers ( Gizmo )
+       2) anything with gizmo in the name
+       3) anything with computer in the name
+       4) the first value
+       """
+       number = get_sane_callback(backend)
+       backend.set_callback_number(number)
 
 
 def _is_not_special(name):