Switched to module level loggers
[gc-dialer] / src / gv_backend.py
index 0b1ca4b..4d6e19c 100644 (file)
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 
 """
-DialCentral - Front end for Google's Grand Central service.
+DialCentral - Front end for Google's GoogleVoice service.
 Copyright (C) 2008  Eric Warnke ericew AT gmail DOT com
 
 This library is free software; you can redistribute it and/or
@@ -33,8 +33,7 @@ import urllib2
 import time
 import datetime
 import itertools
-import warnings
-import traceback
+import logging
 from xml.sax import saxutils
 
 from xml.etree import ElementTree
@@ -47,6 +46,7 @@ except ImportError:
        simplejson = None
 
 
+_moduleLogger = logging.getLogger("gv_backend")
 _TRUE_REGEX = re.compile("true")
 _FALSE_REGEX = re.compile("false")
 
@@ -98,22 +98,9 @@ def itergroup(iterator, count, padValue = None):
        return itertools.izip(*nIterators)
 
 
-def abbrev_relative_date(date):
-       """
-       >>> abbrev_relative_date("42 hours ago")
-       '42 h'
-       >>> abbrev_relative_date("2 days ago")
-       '2 d'
-       >>> abbrev_relative_date("4 weeks ago")
-       '4 w'
-       """
-       parts = date.split(" ")
-       return "%s %s" % (parts[0], parts[1][0])
-
-
 class GVDialer(object):
        """
-       This class encapsulates all of the knowledge necessary to interace with the grandcentral servers
+       This class encapsulates all of the knowledge necessary to interact with the GoogleVoice servers
        the functions include login, setting up a callback number, and initalting a callback
        """
 
@@ -146,8 +133,8 @@ class GVDialer(object):
 
                try:
                        self._grab_account_info()
-               except StandardError, e:
-                       warnings.warn(traceback.format_exc())
+               except Exception, e:
+                       _moduleLogger.exception(str(e))
                        return False
 
                self._browser.cookies.save()
@@ -158,7 +145,7 @@ class GVDialer(object):
 
        def login(self, username, password):
                """
-               Attempt to login to grandcentral
+               Attempt to login to GoogleVoice
                @returns Whether login was successful or not
                """
                if self.is_authed():
@@ -176,7 +163,7 @@ class GVDialer(object):
                try:
                        loginSuccessOrFailurePage = self._browser.download(self._loginURL, loginPostData)
                except urllib2.URLError, e:
-                       warnings.warn(traceback.format_exc())
+                       _moduleLogger.exception(str(e))
                        raise RuntimeError("%s is not accesible" % self._loginURL)
 
                return self.is_authed()
@@ -207,7 +194,7 @@ class GVDialer(object):
                        }
                        callSuccessPage = self._browser.download(self._clicktocallURL, clickToCallData, None, otherData)
                except urllib2.URLError, e:
-                       warnings.warn(traceback.format_exc())
+                       _moduleLogger.exception(str(e))
                        raise RuntimeError("%s is not accesible" % self._clicktocallURL)
 
                if self._gvDialingStrRe.search(callSuccessPage) is None:
@@ -232,7 +219,7 @@ class GVDialer(object):
                        }
                        smsSuccessPage = self._browser.download(self._sendSmsURL, smsData, None, otherData)
                except urllib2.URLError, e:
-                       warnings.warn(traceback.format_exc())
+                       _moduleLogger.exception(str(e))
                        raise RuntimeError("%s is not accesible" % self._sendSmsURL)
 
                return True
@@ -250,7 +237,7 @@ class GVDialer(object):
 
        def get_account_number(self):
                """
-               @returns The grand central phone number
+               @returns The GoogleVoice phone number
                """
                return self._accountNum
 
@@ -296,32 +283,16 @@ class GVDialer(object):
 
        def set_callback_number(self, callbacknumber):
                """
-               Set the number that grandcental calls
+               Set the number that GoogleVoice calls
                @param callbacknumber should be a proper 10 digit number
                """
                self._callbackNumber = callbacknumber
-
-               # Currently this isn't working out in GoogleVoice, but thats ok, we pass the callback on dial
-               #callbackPostData = urllib.urlencode({
-               #       '_rnr_se': self._token,
-               #       'phone': callbacknumber
-               #})
-               #try:
-               #       callbackSetPage = self._browser.download(self._setforwardURL, callbackPostData)
-               #       self._browser.cookies.save()
-               #except urllib2.URLError, e:
-               #       warnings.warn(traceback.format_exc())
-               #       raise RuntimeError("%s is not accesible" % self._setforwardURL)
-
                return True
 
        def get_callback_number(self):
                """
                @returns Current callback number or None
                """
-               #for c in self._browser.cookies:
-               #       if c.name == "gv-ph":
-               #               return c.value
                return self._callbackNumber
 
        def get_recent(self):
@@ -334,7 +305,6 @@ class GVDialer(object):
                ]
                sortedRecent.sort(reverse = True)
                for exactDate, name, number, relativeDate, action in sortedRecent:
-                       relativeDate = abbrev_relative_date(relativeDate)
                        yield name, number, relativeDate, action
 
        def get_addressbooks(self):
@@ -370,7 +340,7 @@ class GVDialer(object):
                                try:
                                        contactsPage = self._browser.download(contactsPageUrl)
                                except urllib2.URLError, e:
-                                       warnings.warn(traceback.format_exc())
+                                       _moduleLogger.exception(str(e))
                                        raise RuntimeError("%s is not accesible" % contactsPageUrl)
                                for contact_match in self._contactsRe.finditer(contactsPage):
                                        contactId = contact_match.group(1)
@@ -397,7 +367,7 @@ class GVDialer(object):
                try:
                        detailPage = self._browser.download(self._contactDetailURL + '/' + contactId)
                except urllib2.URLError, e:
-                       warnings.warn(traceback.format_exc())
+                       _moduleLogger.exception(str(e))
                        raise RuntimeError("%s is not accesible" % self._contactDetailURL)
 
                for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
@@ -412,7 +382,7 @@ class GVDialer(object):
                try:
                        voicemailPage = self._browser.download(self._voicemailURL)
                except urllib2.URLError, e:
-                       warnings.warn(traceback.format_exc())
+                       _moduleLogger.exception(str(e))
                        raise RuntimeError("%s is not accesible" % self._voicemailURL)
                voicemailHtml = self._grab_html(voicemailPage)
                parsedVoicemail = self._parse_voicemail(voicemailHtml)
@@ -421,7 +391,7 @@ class GVDialer(object):
                try:
                        smsPage = self._browser.download(self._smsURL)
                except urllib2.URLError, e:
-                       warnings.warn(traceback.format_exc())
+                       _moduleLogger.exception(str(e))
                        raise RuntimeError("%s is not accesible" % self._smsURL)
                smsHtml = self._grab_html(smsPage)
                parsedSms = self._parse_sms(smsHtml)
@@ -431,7 +401,6 @@ class GVDialer(object):
                sortedMessages = list(allMessages)
                sortedMessages.sort(reverse=True)
                for exactDate, header, number, relativeDate, message in sortedMessages:
-                       relativeDate = abbrev_relative_date(relativeDate)
                        yield header, number, relativeDate, message
 
        def _grab_json(self, flatXml):
@@ -464,7 +433,7 @@ class GVDialer(object):
                if anGroup is not None:
                        self._accountNum = anGroup.group(1)
                else:
-                       warnings.warn("Could not extract account number from GoogleVoice", UserWarning, 2)
+                       _moduleLogger.debug("Could not extract account number from GoogleVoice")
 
                self._callbackNumbers = {}
                for match in self._callbackRe.finditer(page):
@@ -493,14 +462,14 @@ class GVDialer(object):
                @returns Iterable of (personsName, phoneNumber, exact date, relative date, action)
                """
                for action, url in (
-                       ("Recieved", self._receivedCallsURL),
+                       ("Received", self._receivedCallsURL),
                        ("Missed", self._missedCallsURL),
                        ("Placed", self._placedCallsURL),
                ):
                        try:
                                flatXml = self._browser.download(url)
                        except urllib2.URLError, e:
-                               warnings.warn(traceback.format_exc())
+                               _moduleLogger.exception(str(e))
                                raise RuntimeError("%s is not accesible" % url)
 
                        allRecentHtml = self._grab_html(flatXml)
@@ -517,14 +486,24 @@ class GVDialer(object):
                                        header = "Unknown"
                                yield header, recentCallData["number"], exactTime, recentCallData["relTime"], action
 
-       _seperateVoicemailsRegex = re.compile(r"""^\s*<div id="(\w+)"\s* class="gc-message.*?">""", re.MULTILINE | re.DOTALL)
+       _seperateVoicemailsRegex = re.compile(r"""^\s*<div id="(\w+)"\s* class=".*?gc-message.*?">""", re.MULTILINE | re.DOTALL)
        _exactVoicemailTimeRegex = re.compile(r"""<span class="gc-message-time">(.*?)</span>""", re.MULTILINE)
        _relativeVoicemailTimeRegex = re.compile(r"""<span class="gc-message-relative">(.*?)</span>""", re.MULTILINE)
        _voicemailNameRegex = re.compile(r"""<a class=.*?gc-message-name-link.*?>(.*?)</a>""", re.MULTILINE | re.DOTALL)
        _voicemailNumberRegex = re.compile(r"""<input type="hidden" class="gc-text gc-quickcall-ac" value="(.*?)"/>""", re.MULTILINE)
        _prettyVoicemailNumberRegex = re.compile(r"""<span class="gc-message-type">(.*?)</span>""", re.MULTILINE)
        _voicemailLocationRegex = re.compile(r"""<span class="gc-message-location">.*?<a.*?>(.*?)</a></span>""", re.MULTILINE)
-       _voicemailMessageRegex = re.compile(r"""<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>""", re.MULTILINE)
+       #_voicemailMessageRegex = re.compile(r"""<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>""", re.MULTILINE)
+       #_voicemailMessageRegex = re.compile(r"""<a .*? class="gc-message-mni">(.*?)</a>""", re.MULTILINE)
+       _voicemailMessageRegex = re.compile(r"""(<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>|<a .*? class="gc-message-mni">(.*?)</a>)""", re.MULTILINE)
+
+       @staticmethod
+       def _interpret_voicemail_regex(group):
+               quality, content, number = group.group(2), group.group(3), group.group(4)
+               if quality is not None and content is not None:
+                       return quality, content
+               elif number is not None:
+                       return "high", number
 
        def _parse_voicemail(self, voicemailHtml):
                splitVoicemail = self._seperateVoicemailsRegex.split(voicemailHtml)
@@ -546,7 +525,7 @@ class GVDialer(object):
 
                        messageGroups = self._voicemailMessageRegex.finditer(messageHtml)
                        messageParts = (
-                               (group.group(1).strip(), group.group(2).strip())
+                               self._interpret_voicemail_regex(group)
                                for group in messageGroups
                        ) if messageGroups else ()
 
@@ -583,7 +562,7 @@ class GVDialer(object):
                        )).strip()
                        if not message:
                                message = "No Transcription"
-                       yield exactTime, header, voicemailData["number"], voicemailData["relTime"], message
+                       yield exactTime, header, voicemailData["number"], voicemailData["relTime"], (message, )
 
        _smsFromRegex = re.compile(r"""<span class="gc-message-sms-from">(.*?)</span>""", re.MULTILINE | re.DOTALL)
        _smsTextRegex = re.compile(r"""<span class="gc-message-sms-time">(.*?)</span>""", re.MULTILINE | re.DOTALL)
@@ -635,17 +614,16 @@ class GVDialer(object):
                                header = "Unknown"
                        number = messageData["number"]
                        relativeTime = messageData["relTime"]
-                       message = "\n".join((
+                       messages = [
                                "<b>%s</b>: %s" % (messagePart[0], messagePart[-1])
                                for messagePart in messageData["messageParts"]
-                       ))
-                       if not message:
-                               message = "No Transcription"
-                       yield exactTime, header, number, relativeTime, message
+                       ]
+                       if not messages:
+                               messages = ("No Transcription", )
+                       yield exactTime, header, number, relativeTime, messages
 
 
 def test_backend(username, password):
-       import pprint
        backend = GVDialer()
        print "Authenticated: ", backend.is_authed()
        print "Login?: ", backend.login(username, password)
@@ -654,6 +632,7 @@ def test_backend(username, password):
        print "Account: ", backend.get_account_number()
        print "Callback: ", backend.get_callback_number()
        # print "All Callback: ",
+       import pprint
        # pprint.pprint(backend.get_callback_numbers())
        # print "Recent: ",
        # pprint.pprint(list(backend.get_recent()))
@@ -661,5 +640,7 @@ def test_backend(username, password):
        # for contact in backend.get_contacts():
        #       print contact
        #       pprint.pprint(list(backend.get_contact_details(contact[0])))
+       for message in backend.get_messages():
+         pprint.pprint(message)
 
        return backend