import datetime
import itertools
import logging
-from xml.sax import saxutils
+import inspect
+from xml.sax import saxutils
from xml.etree import ElementTree
try:
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
# 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"
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 = (
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
"""
Attempt to login to GoogleVoice
@returns Whether login was successful or not
+ @blocks
"""
self.logout()
galxToken = self._get_token()
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()
self._lastAuthed = 0.0
def is_dnd(self):
+ """
+ @blocks
+ """
isDndPage = self._get_page(self._isDndURL)
dndGroup = self._isDndRe.search(isDndPage)
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
"""
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,
)
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)
"""
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,
return json
def get_feed(self, feed):
+ """
+ @blocks
+ """
actualFeed = "_XML_%s_URL" % feed.upper()
feedUrl = getattr(self, actualFeed)
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)
@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):
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)
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)
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": "<i>%s</i>",
- "med2": "%s",
- "high": "<b>%s</b>",
- }
- 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)
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):
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
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):
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 )
numbers = backend.get_callback_numbers()
priorityOrderedCriteria = [
+ ("\+1747", None),
("1747", None),
+ ("747", None),
(None, "gizmo"),
(None, "computer"),
(None, "sip"),
]
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 = [
- "<b>%s</b>: %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):
("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),
)
-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()