X-Git-Url: http://git.maemo.org/git/?p=theonering;a=blobdiff_plain;f=src%2Fgvoice%2Fbackend.py;h=69e46e4becd08d93a34bce38048e4faa3da3694f;hp=034b7ab59d6a592d8889c5b8f1635d300619e13a;hb=f285dc6899e8ebca47b2cf439c38efeaf3fd43f4;hpb=64dbd8c946755963bb4e3bcbfb14513d5ffad6d8 diff --git a/src/gvoice/backend.py b/src/gvoice/backend.py index 034b7ab..69e46e4 100755 --- a/src/gvoice/backend.py +++ b/src/gvoice/backend.py @@ -35,8 +35,9 @@ import time import datetime import itertools import logging -from xml.sax import saxutils +import inspect +from xml.sax import saxutils from xml.etree import ElementTree try: @@ -48,13 +49,97 @@ except ImportError: import browser_emu -_moduleLogger = logging.getLogger("gvoice.backend") +_moduleLogger = logging.getLogger(__name__) class NetworkError(RuntimeError): pass +class MessageText(object): + + ACCURACY_LOW = "med1" + ACCURACY_MEDIUM = "med2" + ACCURACY_HIGH = "high" + + def __init__(self): + self.accuracy = None + self.text = None + + def __str__(self): + return self.text + + def to_dict(self): + return to_dict(self) + + def __eq__(self, other): + return self.accuracy == other.accuracy and self.text == other.text + + +class Message(object): + + def __init__(self): + self.whoFrom = None + self.body = None + self.when = None + + def __str__(self): + return "%s (%s): %s" % ( + self.whoFrom, + self.when, + "".join(unicode(part) for part in self.body) + ) + + def to_dict(self): + selfDict = to_dict(self) + selfDict["body"] = [text.to_dict() for text in self.body] if self.body is not None else None + return selfDict + + def __eq__(self, other): + return self.whoFrom == other.whoFrom and self.when == other.when and self.body == other.body + + +class Conversation(object): + + TYPE_VOICEMAIL = "Voicemail" + TYPE_SMS = "SMS" + + def __init__(self): + self.type = None + self.id = None + self.contactId = None + self.name = None + self.location = None + self.prettyNumber = None + self.number = None + + self.time = None + self.relTime = None + self.messages = None + self.isRead = None + self.isSpam = None + self.isTrash = None + self.isArchived = None + + def __cmp__(self, other): + cmpValue = cmp(self.contactId, other.contactId) + if cmpValue != 0: + return cmpValue + + cmpValue = cmp(self.time, other.time) + if cmpValue != 0: + return cmpValue + + cmpValue = cmp(self.id, other.id) + if cmpValue != 0: + return cmpValue + + def to_dict(self): + selfDict = to_dict(self) + selfDict["messages"] = [message.to_dict() for message in self.messages] if self.messages is not None else None + return selfDict + + class GVoiceBackend(object): """ This class encapsulates all of the knowledge necessary to interact with the GoogleVoice servers @@ -79,7 +164,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" @@ -96,11 +181,14 @@ class GVoiceBackend(object): self._setDndURL = "https://www.google.com/voice/m/savednd" self._downloadVoicemailURL = SECURE_URL_BASE + "media/send_voicemail/" + self._markAsReadURL = SECURE_URL_BASE + "m/mark" + self._archiveMessageURL = SECURE_URL_BASE + "m/archive" self._XML_SEARCH_URL = SECURE_URL_BASE + "inbox/search/" 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._CSV_CONTACTS_URL = "http://mail.google.com/mail/contacts/data/export" self._XML_RECENT_URL = SECURE_URL_BASE + "inbox/recent/" self.XML_FEEDS = ( @@ -149,6 +237,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 @@ -196,6 +285,7 @@ class GVoiceBackend(object): """ Attempt to login to GoogleVoice @returns Whether login was successful or not + @blocks """ self.logout() galxToken = self._get_token() @@ -216,6 +306,14 @@ class GVoiceBackend(object): self._lastAuthed = time.time() return True + def persist(self): + self._browser.save_cookies() + + def shutdown(self): + self._browser.save_cookies() + self._token = None + self._lastAuthed = 0.0 + def logout(self): self._browser.clear_cookies() self._browser.save_cookies() @@ -223,6 +321,9 @@ class GVoiceBackend(object): self._lastAuthed = 0.0 def is_dnd(self): + """ + @blocks + """ isDndPage = self._get_page(self._isDndURL) dndGroup = self._isDndRe.search(isDndPage) @@ -233,16 +334,19 @@ class GVoiceBackend(object): return isDnd def set_dnd(self, doNotDisturb): + """ + @blocks + """ dndPostData = { "doNotDisturb": 1 if doNotDisturb else 0, - "_rnr_se": self._token, } - dndPage = self._get_page(self._setDndURL, dndPostData) + dndPage = self._get_page_with_token(self._setDndURL, dndPostData) def call(self, outgoingNumber): """ This is the main function responsible for initating the callback + @blocks """ outgoingNumber = self._send_validation(outgoingNumber) subscriberNumber = None @@ -268,6 +372,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, @@ -279,13 +384,20 @@ 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, - 'text': message + 'phoneNumber': flattenedPhoneNumbers, + 'text': unicode(message).encode("utf-8"), }, ) self._parse_with_validation(page) @@ -294,6 +406,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, @@ -303,6 +416,9 @@ class GVoiceBackend(object): return json def get_feed(self, feed): + """ + @blocks + """ actualFeed = "_XML_%s_URL" % feed.upper() feedUrl = getattr(self, actualFeed) @@ -317,7 +433,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) @@ -352,6 +469,7 @@ class GVoiceBackend(object): @param callbacknumber should be a proper 10 digit number """ self._callbackNumber = callbacknumber + _moduleLogger.info("Callback number changed: %r" % self._callbackNumber) return True def get_callback_number(self): @@ -363,51 +481,82 @@ 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), - ("Missed", self._XML_MISSED_URL), - ("Placed", self._XML_PLACED_URL), - ): - flatXml = self._get_page(url) - - allRecentHtml = self._grab_html(flatXml) - allRecentData = self._parse_voicemail(allRecentHtml) - for recentCallData in allRecentData: - recentCallData["action"] = action - yield recentCallData + recentPages = [ + (action, self._get_page(url)) + for action, url in ( + ("Received", self._XML_RECEIVED_URL), + ("Missed", self._XML_MISSED_URL), + ("Placed", self._XML_PLACED_URL), + ) + ] + return self._parse_recent(recentPages) 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) - 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 + return self._process_contacts(page) + + def get_csv_contacts(self): + data = { + "groupToExport": "mine", + "exportType": "ALL", + "out": "OUTLOOK_CSV", + } + encodedData = urllib.urlencode(data) + contacts = self._get_page(self._CSV_CONTACTS_URL+"?"+encodedData) + return contacts - def get_messages(self): + def get_voicemails(self): + """ + @blocks + """ voicemailPage = self._get_page(self._XML_VOICEMAIL_URL) voicemailHtml = self._grab_html(voicemailPage) voicemailJson = self._grab_json(voicemailPage) + if voicemailJson is None: + return () parsedVoicemail = self._parse_voicemail(voicemailHtml) - voicemails = self._merge_messages(parsedVoicemail, voicemailJson) - decoratedVoicemails = self._decorate_voicemail(voicemails) + 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) + if smsJson is None: + return () parsedSms = self._parse_sms(smsHtml) - smss = self._merge_messages(parsedSms, smsJson) - decoratedSms = self._decorate_sms(smss) + smss = self._merge_conversation_sources(parsedSms, smsJson) + return smss + + def mark_message(self, messageId, asRead): + """ + @blocks + """ + postData = { + "read": 1 if asRead else 0, + "id": messageId, + } + + markPage = self._get_page(self._markAsReadURL, postData) + + def archive_message(self, messageId): + """ + @blocks + """ + postData = { + "id": messageId, + } - allMessages = itertools.chain(decoratedVoicemails, decoratedSms) - return allMessages + markPage = self._get_page(self._archiveMessageURL, postData) def _grab_json(self, flatXml): xmlTree = ElementTree.fromstring(flatXml) @@ -447,26 +596,32 @@ 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 - @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_recent(self, recentPages): + for action, flatXml in recentPages: + allRecentHtml = self._grab_html(flatXml) + allRecentData = self._parse_history(allRecentHtml) + for recentCallData in allRecentData: + recentCallData["action"] = action + yield recentCallData - def _parse_voicemail(self, voicemailHtml): - splitVoicemail = self._seperateVoicemailsRegex.split(voicemailHtml) + def _process_contacts(self, page): + 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 _parse_history(self, historyHtml): + splitVoicemail = self._seperateVoicemailsRegex.split(historyHtml) 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) @@ -481,60 +636,102 @@ class GVoiceBackend(object): contactIdGroup = self._messagesContactIDRegex.search(messageHtml) contactId = contactIdGroup.group(1).strip() if contactIdGroup else "" - messageGroups = self._voicemailMessageRegex.finditer(messageHtml) - messageParts = ( - self._interpret_voicemail_regex(group) - for group in messageGroups - ) if messageGroups else () - yield { "id": messageId.strip(), "contactId": contactId, - "name": name, + "name": unescape(name), "time": exactTime, "relTime": relativeTime, "prettyNumber": prettyNumber, "number": number, - "location": location, - "messageParts": messageParts, - "type": "Voicemail", + "location": unescape(location), } - def _decorate_voicemail(self, parsedVoicemails): - messagePartFormat = { - "med1": "%s", - "med2": "%s", - "high": "%s", - } - for voicemailData in parsedVoicemails: - message = " ".join(( - messagePartFormat[quality] % part - for (quality, part) in voicemailData["messageParts"] - )).strip() - if not message: - message = "No Transcription" - whoFrom = voicemailData["name"] - when = voicemailData["time"] - voicemailData["messageParts"] = ((whoFrom, message, when), ) - yield voicemailData + @staticmethod + def _interpret_voicemail_regex(group): + quality, content, number = group.group(2), group.group(3), group.group(4) + text = MessageText() + if quality is not None and content is not None: + text.accuracy = quality + text.text = content + return text + elif number is not None: + text.accuracy = MessageText.ACCURACY_HIGH + text.text = number + return text + + def _parse_voicemail(self, voicemailHtml): + splitVoicemail = self._seperateVoicemailsRegex.split(voicemailHtml) + for messageId, messageHtml in itergroup(splitVoicemail[1:], 2): + conv = Conversation() + conv.type = Conversation.TYPE_VOICEMAIL + conv.id = messageId.strip() + + exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml) + exactTimeText = exactTimeGroup.group(1).strip() if exactTimeGroup else "" + conv.time = google_strptime(exactTimeText) + relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml) + conv.relTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else "" + locationGroup = self._voicemailLocationRegex.search(messageHtml) + conv.location = unescape(locationGroup.group(1).strip() if locationGroup else "") + + nameGroup = self._voicemailNameRegex.search(messageHtml) + conv.name = unescape(nameGroup.group(1).strip() if nameGroup else "") + numberGroup = self._voicemailNumberRegex.search(messageHtml) + conv.number = numberGroup.group(1).strip() if numberGroup else "" + prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml) + conv.prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else "" + contactIdGroup = self._messagesContactIDRegex.search(messageHtml) + conv.contactId = contactIdGroup.group(1).strip() if contactIdGroup else "" + + messageGroups = self._voicemailMessageRegex.finditer(messageHtml) + messageParts = [ + self._interpret_voicemail_regex(group) + for group in messageGroups + ] if messageGroups else ((MessageText.ACCURACY_LOW, "No Transcription"), ) + message = Message() + message.body = messageParts + message.whoFrom = conv.name + message.when = conv.time.strftime("%I:%M %p") + conv.messages = (message, ) + + yield conv + + @staticmethod + def _interpret_sms_message_parts(fromPart, textPart, timePart): + text = MessageText() + text.accuracy = MessageText.ACCURACY_MEDIUM + text.text = textPart + + message = Message() + message.body = (text, ) + message.whoFrom = fromPart + message.when = timePart + + return message def _parse_sms(self, smsHtml): splitSms = self._seperateVoicemailsRegex.split(smsHtml) for messageId, messageHtml in itergroup(splitSms[1:], 2): + conv = Conversation() + conv.type = Conversation.TYPE_SMS + conv.id = messageId.strip() + exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml) - exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else "" - exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p") + exactTimeText = exactTimeGroup.group(1).strip() if exactTimeGroup else "" + conv.time = google_strptime(exactTimeText) relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml) - relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else "" + conv.relTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else "" + conv.location = "" nameGroup = self._voicemailNameRegex.search(messageHtml) - name = nameGroup.group(1).strip() if nameGroup else "" + conv.name = unescape(nameGroup.group(1).strip() if nameGroup else "") numberGroup = self._voicemailNumberRegex.search(messageHtml) - number = numberGroup.group(1).strip() if numberGroup else "" + conv.number = numberGroup.group(1).strip() if numberGroup else "" prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml) - prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else "" + conv.prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else "" contactIdGroup = self._messagesContactIDRegex.search(messageHtml) - contactId = contactIdGroup.group(1).strip() if contactIdGroup else "" + conv.contactId = contactIdGroup.group(1).strip() if contactIdGroup else "" fromGroups = self._smsFromRegex.finditer(messageHtml) fromParts = (group.group(1).strip() for group in fromGroups) @@ -544,32 +741,19 @@ class GVoiceBackend(object): timeParts = (group.group(1).strip() for group in timeGroups) messageParts = itertools.izip(fromParts, textParts, timeParts) + messages = [self._interpret_sms_message_parts(*parts) for parts in messageParts] + conv.messages = messages - yield { - "id": messageId.strip(), - "contactId": contactId, - "name": name, - "time": exactTime, - "relTime": relativeTime, - "prettyNumber": prettyNumber, - "number": number, - "location": "", - "messageParts": messageParts, - "type": "Texts", - } - - def _decorate_sms(self, parsedTexts): - return parsedTexts + yield conv @staticmethod - def _merge_messages(parsedMessages, json): + def _merge_conversation_sources(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"] - message["isArchived"] = "inbox" not in jsonItem["labels"] + jsonItem = json["messages"][message.id] + message.isRead = jsonItem["isRead"] + message.isSpam = jsonItem["isSpam"] + message.isTrash = jsonItem["isTrash"] + message.isArchived = "inbox" not in jsonItem["labels"] yield message def _get_page(self, url, data = None, refererUrl = None): @@ -602,6 +786,31 @@ class GVoiceBackend(object): return json +_UNESCAPE_ENTITIES = { + """: '"', + " ": " ", + "'": "'", +} + + +def unescape(text): + plain = saxutils.unescape(text, _UNESCAPE_ENTITIES) + return plain + + +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.endswith("PM"): + parsedTime += datetime.timedelta(hours=12) + return parsedTime + + def itergroup(iterator, count, padValue = None): """ Iterate in groups of 'count' values. If there @@ -638,9 +847,16 @@ def itergroup(iterator, count, padValue = None): def safe_eval(s): _TRUE_REGEX = re.compile("true") _FALSE_REGEX = re.compile("false") + _COMMENT_REGEX = re.compile("^\s+//.*$", re.M) s = _TRUE_REGEX.sub("True", s) s = _FALSE_REGEX.sub("False", s) - return eval(s, {}, {}) + s = _COMMENT_REGEX.sub("#", s) + try: + results = eval(s, {}, {}) + except SyntaxError: + _moduleLogger.exception("Oops") + results = None + return results def _fake_parse_json(flattened): @@ -675,19 +891,21 @@ def validate_response(response): Validates that the JSON response is A-OK """ try: - assert 'ok' in response and response['ok'] + assert response is not None + assert 'ok' in response + assert response['ok'] except AssertionError: raise RuntimeError('There was a problem with GV: %s' % 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 ) @@ -698,7 +916,9 @@ def set_sane_callback(backend): numbers = backend.get_callback_numbers() priorityOrderedCriteria = [ + ("\+1747", None), ("1747", None), + ("747", None), (None, "gizmo"), (None, "computer"), (None, "sip"), @@ -706,111 +926,40 @@ 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 - - -def sort_messages(allMessages): - sortableAllMessages = [ - (message["time"], message) - for message in allMessages - ] - sortableAllMessages.sort(reverse=True) - return ( - message - for (exactTime, message) in sortableAllMessages - ) + return number -def decorate_recent(recentCallData): +def set_sane_callback(backend): """ - @returns (personsName, phoneNumber, date, action) + 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 """ - contactId = recentCallData["contactId"] - if recentCallData["name"]: - header = recentCallData["name"] - elif recentCallData["prettyNumber"]: - header = recentCallData["prettyNumber"] - elif recentCallData["location"]: - header = recentCallData["location"] - else: - header = "Unknown" + number = get_sane_callback(backend) + backend.set_callback_number(number) - number = recentCallData["number"] - relTime = recentCallData["relTime"] - action = recentCallData["action"] - return contactId, header, number, relTime, action +def _is_not_special(name): + return not name.startswith("_") and name[0].lower() == name[0] and "_" not in name -def decorate_message(messageData): - contactId = messageData["contactId"] - exactTime = messageData["time"] - if messageData["name"]: - header = messageData["name"] - elif messageData["prettyNumber"]: - header = messageData["prettyNumber"] - else: - header = "Unknown" - number = messageData["number"] - relativeTime = messageData["relTime"] - - messageParts = list(messageData["messageParts"]) - if len(messageParts) == 0: - messages = ("No Transcription", ) - elif len(messageParts) == 1: - messages = (messageParts[0][1], ) - else: - messages = [ - "%s: %s" % (messagePart[0], messagePart[1]) - for messagePart in messageParts - ] - decoratedResults = contactId, header, number, relativeTime, messages - return decoratedResults - - -def test_backend(username, password): - backend = GVoiceBackend() - print "Authenticated: ", backend.is_authed() - if not backend.is_authed(): - print "Login?: ", backend.login(username, password) - print "Authenticated: ", backend.is_authed() - #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) - #print "Is Dnd: ", backend.is_dnd() - - #print "Token: ", backend._token - #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: " - #for data in backend.get_recent(): - # pprint.pprint(data) - #for data in sort_messages(backend.get_recent()): - # pprint.pprint(decorate_recent(data)) - #pprint.pprint(list(backend.get_recent())) - - print "Contacts: ", - for contact in backend.get_contacts(): - pprint.pprint(contact) - - #print "Messages: ", - #for message in backend.get_messages(): - # pprint.pprint(message) - #for message in sort_messages(backend.get_messages()): - # pprint.pprint(decorate_message(message)) - - return backend +def to_dict(obj): + members = inspect.getmembers(obj) + return dict((name, value) for (name, value) in members if _is_not_special(name)) def grab_debug_info(username, password): @@ -830,6 +979,7 @@ def grab_debug_info(username, password): ("isdnd", backend._isDndURL), ("account", backend._XML_ACCOUNT_URL), ("contacts", backend._XML_CONTACTS_URL), + ("csv", backend._CSV_CONTACTS_URL), ("voicemail", backend._XML_VOICEMAIL_URL), ("sms", backend._XML_SMS_URL), @@ -890,10 +1040,16 @@ def grab_debug_info(username, password): ) -if __name__ == "__main__": +def main(): import sys logging.basicConfig(level=logging.DEBUG) - if True: - grab_debug_info(sys.argv[1], sys.argv[2]) - else: - test_backend(sys.argv[1], sys.argv[2]) + args = sys.argv + if 3 <= len(args): + username = args[1] + password = args[2] + + grab_debug_info(username, password) + + +if __name__ == "__main__": + main()