4 DialCentral - Front end for Google's GoogleVoice service.
5 Copyright (C) 2008 Eric Warnke ericew AT gmail DOT com
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 Google Voice backend code
24 http://thatsmith.com/2009/03/google-voice-addon-for-firefox/
25 http://posttopic.com/topic/google-voice-add-on-development
28 from __future__ import with_statement
38 from xml.sax import saxutils
40 from xml.etree import ElementTree
50 _moduleLogger = logging.getLogger("gvoice.backend")
54 _TRUE_REGEX = re.compile("true")
55 _FALSE_REGEX = re.compile("false")
56 s = _TRUE_REGEX.sub("True", s)
57 s = _FALSE_REGEX.sub("False", s)
58 return eval(s, {}, {})
61 if simplejson is None:
62 def parse_json(flattened):
63 return safe_eval(flattened)
65 def parse_json(flattened):
66 return simplejson.loads(flattened)
69 def itergroup(iterator, count, padValue = None):
71 Iterate in groups of 'count' values. If there
72 aren't enough values, the last result is padded with
75 >>> for val in itergroup([1, 2, 3, 4, 5, 6], 3):
79 >>> for val in itergroup([1, 2, 3, 4, 5, 6], 3):
83 >>> for val in itergroup([1, 2, 3, 4, 5, 6, 7], 3):
88 >>> for val in itergroup("123456", 3):
92 >>> for val in itergroup("123456", 3):
93 ... print repr("".join(val))
97 paddedIterator = itertools.chain(iterator, itertools.repeat(padValue, count-1))
98 nIterators = (paddedIterator, ) * count
99 return itertools.izip(*nIterators)
102 class NetworkError(RuntimeError):
106 class GVoiceBackend(object):
108 This class encapsulates all of the knowledge necessary to interact with the GoogleVoice servers
109 the functions include login, setting up a callback number, and initalting a callback
112 def __init__(self, cookieFile = None):
113 # Important items in this function are the setup of the browser emulation and cookie file
114 self._browser = browser_emu.MozillaEmulator(1)
115 if cookieFile is None:
116 cookieFile = os.path.join(os.path.expanduser("~"), ".gv_cookies.txt")
117 self._browser.cookies.filename = cookieFile
118 if os.path.isfile(cookieFile):
119 self._browser.cookies.load()
122 self._accountNum = ""
123 self._lastAuthed = 0.0
124 self._callbackNumber = ""
125 self._callbackNumbers = {}
127 # Suprisingly, moving all of these from class to self sped up startup time
129 self._validateRe = re.compile("^[0-9]{10,}$")
131 self._forwardURL = "https://www.google.com/voice/mobile/phones"
132 self._tokenURL = "http://www.google.com/voice/m"
133 self._loginURL = "https://www.google.com/accounts/ServiceLoginAuth"
134 self._galxRe = re.compile(r"""<input.*?name="GALX".*?value="(.*?)".*?/>""", re.MULTILINE | re.DOTALL)
135 self._tokenRe = re.compile(r"""<input.*?name="_rnr_se".*?value="(.*?)"\s*/>""")
136 self._accountNumRe = re.compile(r"""<b class="ms\d">(.{14})</b></div>""")
137 self._callbackRe = re.compile(r"""\s+(.*?):\s*(.*?)<br\s*/>\s*$""", re.M)
139 self._isDndURL = "https://www.google.com/voice/m/donotdisturb"
140 self._isDndRe = re.compile(r"""<input.*?id="doNotDisturb".*?checked="(.*?)"\s*/>""")
141 self._setDndURL = "https://www.google.com/voice/m/savednd"
143 self._gvDialingStrRe = re.compile("This may take a few seconds", re.M)
144 self._clicktocallURL = "https://www.google.com/voice/m/sendcall"
145 self._sendSmsURL = "https://www.google.com/voice/m/sendsms"
147 self._recentCallsURL = "https://www.google.com/voice/inbox/recent/"
148 self._placedCallsURL = "https://www.google.com/voice/inbox/recent/placed/"
149 self._receivedCallsURL = "https://www.google.com/voice/inbox/recent/received/"
150 self._missedCallsURL = "https://www.google.com/voice/inbox/recent/missed/"
152 self._contactsRe = re.compile(r"""<a href="/voice/m/contact/(\d+)">(.*?)</a>""", re.S)
153 self._contactsNextRe = re.compile(r""".*<a href="/voice/m/contacts(\?p=\d+)">Next.*?</a>""", re.S)
154 self._contactsURL = "https://www.google.com/voice/mobile/contacts"
155 self._contactDetailPhoneRe = re.compile(r"""<div.*?>([0-9+\-\(\) \t]+?)<span.*?>\((\w+)\)</span>""", re.S)
156 self._contactDetailURL = "https://www.google.com/voice/mobile/contact"
158 self._voicemailURL = "https://www.google.com/voice/inbox/recent/voicemail/"
159 self._smsURL = "https://www.google.com/voice/inbox/recent/sms/"
160 self._seperateVoicemailsRegex = re.compile(r"""^\s*<div id="(\w+)"\s* class=".*?gc-message.*?">""", re.MULTILINE | re.DOTALL)
161 self._exactVoicemailTimeRegex = re.compile(r"""<span class="gc-message-time">(.*?)</span>""", re.MULTILINE)
162 self._relativeVoicemailTimeRegex = re.compile(r"""<span class="gc-message-relative">(.*?)</span>""", re.MULTILINE)
163 self._voicemailNameRegex = re.compile(r"""<a class=.*?gc-message-name-link.*?>(.*?)</a>""", re.MULTILINE | re.DOTALL)
164 self._voicemailNumberRegex = re.compile(r"""<input type="hidden" class="gc-text gc-quickcall-ac" value="(.*?)"/>""", re.MULTILINE)
165 self._prettyVoicemailNumberRegex = re.compile(r"""<span class="gc-message-type">(.*?)</span>""", re.MULTILINE)
166 self._voicemailLocationRegex = re.compile(r"""<span class="gc-message-location">.*?<a.*?>(.*?)</a></span>""", re.MULTILINE)
167 self._messagesContactID = re.compile(r"""<a class=".*?gc-message-name-link.*?">.*?</a>\s*?<span .*?>(.*?)</span>""", re.MULTILINE)
168 self._voicemailMessageRegex = re.compile(r"""(<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>|<a .*? class="gc-message-mni">(.*?)</a>)""", re.MULTILINE)
169 self._smsFromRegex = re.compile(r"""<span class="gc-message-sms-from">(.*?)</span>""", re.MULTILINE | re.DOTALL)
170 self._smsTimeRegex = re.compile(r"""<span class="gc-message-sms-time">(.*?)</span>""", re.MULTILINE | re.DOTALL)
171 self._smsTextRegex = re.compile(r"""<span class="gc-message-sms-text">(.*?)</span>""", re.MULTILINE | re.DOTALL)
173 def is_authed(self, force = False):
175 Attempts to detect a current session
176 @note Once logged in try not to reauth more than once a minute.
177 @returns If authenticated
179 if (time.time() - self._lastAuthed) < 120 and not force:
183 page = self._browser.download(self._forwardURL)
184 self._grab_account_info(page)
186 _moduleLogger.exception(str(e))
189 self._browser.cookies.save()
190 self._lastAuthed = time.time()
193 def _get_token(self):
195 tokenPage = self._browser.download(self._tokenURL)
196 except urllib2.URLError, e:
197 _moduleLogger.exception("Translating error: %s" % str(e))
198 raise NetworkError("%s is not accesible" % self._loginURL)
199 galxTokens = self._galxRe.search(tokenPage)
200 if galxTokens is not None:
201 galxToken = galxTokens.group(1)
204 _moduleLogger.debug("Could not grab GALX token")
207 def _login(self, username, password, token):
208 loginPostData = urllib.urlencode({
211 'service': "grandcentral",
214 "PersistentCookie": "yes",
216 "continue": self._forwardURL,
220 loginSuccessOrFailurePage = self._browser.download(self._loginURL, loginPostData)
221 except urllib2.URLError, e:
222 _moduleLogger.exception("Translating error: %s" % str(e))
223 raise NetworkError("%s is not accesible" % self._loginURL)
224 return loginSuccessOrFailurePage
226 def login(self, username, password):
228 Attempt to login to GoogleVoice
229 @returns Whether login was successful or not
232 galxToken = self._get_token()
233 loginSuccessOrFailurePage = self._login(username, password, galxToken)
236 self._grab_account_info(loginSuccessOrFailurePage)
238 # Retry in case the redirect failed
239 # luckily is_authed does everything we need for a retry
240 loggedIn = self.is_authed(True)
242 _moduleLogger.exception(str(e))
244 _moduleLogger.info("Redirection failed on initial login attempt, auto-corrected for this")
246 self._browser.cookies.save()
247 self._lastAuthed = time.time()
251 self._lastAuthed = 0.0
252 self._browser.cookies.clear()
253 self._browser.cookies.save()
257 isDndPage = self._browser.download(self._isDndURL)
258 except urllib2.URLError, e:
259 _moduleLogger.exception("Translating error: %s" % str(e))
260 raise NetworkError("%s is not accesible" % self._isDndURL)
262 dndGroup = self._isDndRe.search(isDndPage)
265 dndStatus = dndGroup.group(1)
266 isDnd = True if dndStatus.strip().lower() == "true" else False
269 def set_dnd(self, doNotDisturb):
270 dndPostData = urllib.urlencode({
271 "doNotDisturb": 1 if doNotDisturb else 0,
272 "_rnr_se": self._token,
276 dndPage = self._browser.download(self._setDndURL, dndPostData)
277 except urllib2.URLError, e:
278 _moduleLogger.exception("Translating error: %s" % str(e))
279 raise NetworkError("%s is not accesible" % self._setDndURL)
281 def dial(self, number):
283 This is the main function responsible for initating the callback
285 number = self._send_validation(number)
287 clickToCallData = urllib.urlencode({
289 "phone": self._callbackNumber,
290 "_rnr_se": self._token,
293 'Referer' : 'https://google.com/voice/m/callsms',
295 callSuccessPage = self._browser.download(self._clicktocallURL, clickToCallData, None, otherData)
296 except urllib2.URLError, e:
297 _moduleLogger.exception("Translating error: %s" % str(e))
298 raise NetworkError("%s is not accesible" % self._clicktocallURL)
300 if self._gvDialingStrRe.search(callSuccessPage) is None:
301 raise RuntimeError("Google Voice returned an error")
305 def send_sms(self, number, message):
306 number = self._send_validation(number)
308 smsData = urllib.urlencode({
311 "_rnr_se": self._token,
316 'Referer' : 'https://google.com/voice/m/sms',
318 smsSuccessPage = self._browser.download(self._sendSmsURL, smsData, None, otherData)
319 except urllib2.URLError, e:
320 _moduleLogger.exception("Translating error: %s" % str(e))
321 raise NetworkError("%s is not accesible" % self._sendSmsURL)
325 def is_valid_syntax(self, number):
327 @returns If This number be called ( syntax validation only )
329 return self._validateRe.match(number) is not None
331 def get_account_number(self):
333 @returns The GoogleVoice phone number
335 return self._accountNum
337 def get_callback_numbers(self):
339 @returns a dictionary mapping call back numbers to descriptions
340 @note These results are cached for 30 minutes.
342 if not self.is_authed():
344 return self._callbackNumbers
346 def set_callback_number(self, callbacknumber):
348 Set the number that GoogleVoice calls
349 @param callbacknumber should be a proper 10 digit number
351 self._callbackNumber = callbacknumber
354 def get_callback_number(self):
356 @returns Current callback number or None
358 return self._callbackNumber
360 def get_recent(self):
362 @returns Iterable of (personsName, phoneNumber, exact date, relative date, action)
365 ("Received", self._receivedCallsURL),
366 ("Missed", self._missedCallsURL),
367 ("Placed", self._placedCallsURL),
370 flatXml = self._browser.download(url)
371 except urllib2.URLError, e:
372 _moduleLogger.exception("Translating error: %s" % str(e))
373 raise NetworkError("%s is not accesible" % url)
375 allRecentHtml = self._grab_html(flatXml)
376 allRecentData = self._parse_voicemail(allRecentHtml)
377 for recentCallData in allRecentData:
378 recentCallData["action"] = action
381 def get_contacts(self):
383 @returns Iterable of (contact id, contact name)
385 contactsPagesUrls = [self._contactsURL]
386 for contactsPageUrl in contactsPagesUrls:
388 contactsPage = self._browser.download(contactsPageUrl)
389 except urllib2.URLError, e:
390 _moduleLogger.exception("Translating error: %s" % str(e))
391 raise NetworkError("%s is not accesible" % contactsPageUrl)
392 for contact_match in self._contactsRe.finditer(contactsPage):
393 contactId = contact_match.group(1)
394 contactName = saxutils.unescape(contact_match.group(2))
395 contact = contactId, contactName
398 next_match = self._contactsNextRe.match(contactsPage)
399 if next_match is not None:
400 newContactsPageUrl = self._contactsURL + next_match.group(1)
401 contactsPagesUrls.append(newContactsPageUrl)
403 def get_contact_details(self, contactId):
405 @returns Iterable of (Phone Type, Phone Number)
408 detailPage = self._browser.download(self._contactDetailURL + '/' + contactId)
409 except urllib2.URLError, e:
410 _moduleLogger.exception("Translating error: %s" % str(e))
411 raise NetworkError("%s is not accesible" % self._contactDetailURL)
413 for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
414 phoneNumber = detail_match.group(1)
415 phoneType = saxutils.unescape(detail_match.group(2))
416 yield (phoneType, phoneNumber)
418 def get_messages(self):
420 voicemailPage = self._browser.download(self._voicemailURL)
421 except urllib2.URLError, e:
422 _moduleLogger.exception("Translating error: %s" % str(e))
423 raise NetworkError("%s is not accesible" % self._voicemailURL)
424 voicemailHtml = self._grab_html(voicemailPage)
425 voicemailJson = self._grab_json(voicemailPage)
426 parsedVoicemail = self._parse_voicemail(voicemailHtml)
427 voicemails = self._merge_messages(parsedVoicemail, voicemailJson)
428 decoratedVoicemails = self._decorate_voicemail(voicemails)
431 smsPage = self._browser.download(self._smsURL)
432 except urllib2.URLError, e:
433 _moduleLogger.exception("Translating error: %s" % str(e))
434 raise NetworkError("%s is not accesible" % self._smsURL)
435 smsHtml = self._grab_html(smsPage)
436 smsJson = self._grab_json(smsPage)
437 parsedSms = self._parse_sms(smsHtml)
438 smss = self._merge_messages(parsedSms, smsJson)
439 decoratedSms = self._decorate_sms(smss)
441 allMessages = itertools.chain(decoratedVoicemails, decoratedSms)
444 def _grab_json(self, flatXml):
445 xmlTree = ElementTree.fromstring(flatXml)
446 jsonElement = xmlTree.getchildren()[0]
447 flatJson = jsonElement.text
448 jsonTree = parse_json(flatJson)
451 def _grab_html(self, flatXml):
452 xmlTree = ElementTree.fromstring(flatXml)
453 htmlElement = xmlTree.getchildren()[1]
454 flatHtml = htmlElement.text
457 def _grab_account_info(self, page):
458 tokenGroup = self._tokenRe.search(page)
459 if tokenGroup is None:
460 raise RuntimeError("Could not extract authentication token from GoogleVoice")
461 self._token = tokenGroup.group(1)
463 anGroup = self._accountNumRe.search(page)
464 if anGroup is not None:
465 self._accountNum = anGroup.group(1)
467 _moduleLogger.debug("Could not extract account number from GoogleVoice")
469 self._callbackNumbers = {}
470 for match in self._callbackRe.finditer(page):
471 callbackNumber = match.group(2)
472 callbackName = match.group(1)
473 self._callbackNumbers[callbackNumber] = callbackName
474 if len(self._callbackNumbers) == 0:
475 _moduleLogger.debug("Could not extract callback numbers from GoogleVoice (the troublesome page follows):\n%s" % page)
477 def _send_validation(self, number):
478 if not self.is_valid_syntax(number):
479 raise ValueError('Number is not valid: "%s"' % number)
480 elif not self.is_authed():
481 raise RuntimeError("Not Authenticated")
483 if len(number) == 11 and number[0] == 1:
484 # Strip leading 1 from 11 digit dialing
489 def _interpret_voicemail_regex(group):
490 quality, content, number = group.group(2), group.group(3), group.group(4)
491 if quality is not None and content is not None:
492 return quality, content
493 elif number is not None:
494 return "high", number
496 def _parse_voicemail(self, voicemailHtml):
497 splitVoicemail = self._seperateVoicemailsRegex.split(voicemailHtml)
498 for messageId, messageHtml in itergroup(splitVoicemail[1:], 2):
499 exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
500 exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
501 exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p")
502 relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
503 relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
504 locationGroup = self._voicemailLocationRegex.search(messageHtml)
505 location = locationGroup.group(1).strip() if locationGroup else ""
507 nameGroup = self._voicemailNameRegex.search(messageHtml)
508 name = nameGroup.group(1).strip() if nameGroup else ""
509 numberGroup = self._voicemailNumberRegex.search(messageHtml)
510 number = numberGroup.group(1).strip() if numberGroup else ""
511 prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml)
512 prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
513 contactIdGroup = self._messagesContactID.search(messageHtml)
514 contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
516 messageGroups = self._voicemailMessageRegex.finditer(messageHtml)
518 self._interpret_voicemail_regex(group)
519 for group in messageGroups
520 ) if messageGroups else ()
523 "id": messageId.strip(),
524 "contactId": contactId,
527 "relTime": relativeTime,
528 "prettyNumber": prettyNumber,
530 "location": location,
531 "messageParts": messageParts,
535 def _decorate_voicemail(self, parsedVoicemails):
536 messagePartFormat = {
541 for voicemailData in parsedVoicemails:
543 messagePartFormat[quality] % part
544 for (quality, part) in voicemailData["messageParts"]
547 message = "No Transcription"
548 whoFrom = voicemailData["name"]
549 when = voicemailData["time"]
550 voicemailData["messageParts"] = ((whoFrom, message, when), )
553 def _parse_sms(self, smsHtml):
554 splitSms = self._seperateVoicemailsRegex.split(smsHtml)
555 for messageId, messageHtml in itergroup(splitSms[1:], 2):
556 exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
557 exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
558 exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p")
559 relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
560 relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
562 nameGroup = self._voicemailNameRegex.search(messageHtml)
563 name = nameGroup.group(1).strip() if nameGroup else ""
564 numberGroup = self._voicemailNumberRegex.search(messageHtml)
565 number = numberGroup.group(1).strip() if numberGroup else ""
566 prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml)
567 prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
568 contactIdGroup = self._messagesContactID.search(messageHtml)
569 contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
571 fromGroups = self._smsFromRegex.finditer(messageHtml)
572 fromParts = (group.group(1).strip() for group in fromGroups)
573 textGroups = self._smsTextRegex.finditer(messageHtml)
574 textParts = (group.group(1).strip() for group in textGroups)
575 timeGroups = self._smsTimeRegex.finditer(messageHtml)
576 timeParts = (group.group(1).strip() for group in timeGroups)
578 messageParts = itertools.izip(fromParts, textParts, timeParts)
581 "id": messageId.strip(),
582 "contactId": contactId,
585 "relTime": relativeTime,
586 "prettyNumber": prettyNumber,
589 "messageParts": messageParts,
593 def _decorate_sms(self, parsedTexts):
597 def _merge_messages(parsedMessages, json):
598 for message in parsedMessages:
600 jsonItem = json["messages"][id]
601 message["isRead"] = jsonItem["isRead"]
602 message["isSpam"] = jsonItem["isSpam"]
603 message["isTrash"] = jsonItem["isTrash"]
604 message["isArchived"] = "inbox" not in jsonItem["labels"]
608 def set_sane_callback(backend):
610 Try to set a sane default callback number on these preferences
611 1) 1747 numbers ( Gizmo )
612 2) anything with gizmo in the name
613 3) anything with computer in the name
616 numbers = backend.get_callback_numbers()
618 priorityOrderedCriteria = [
626 for numberCriteria, descriptionCriteria in priorityOrderedCriteria:
627 for number, description in numbers.iteritems():
628 if numberCriteria is not None and re.compile(numberCriteria).match(number) is None:
630 if descriptionCriteria is not None and re.compile(descriptionCriteria).match(description) is None:
632 backend.set_callback_number(number)
636 def sort_messages(allMessages):
637 sortableAllMessages = [
638 (message["time"], message)
639 for message in allMessages
641 sortableAllMessages.sort(reverse=True)
644 for (exactTime, message) in sortableAllMessages
648 def decorate_recent(recentCallData):
650 @returns (personsName, phoneNumber, date, action)
652 contactId = recentCallData["contactId"]
653 if recentCallData["name"]:
654 header = recentCallData["name"]
655 elif recentCallData["prettyNumber"]:
656 header = recentCallData["prettyNumber"]
657 elif recentCallData["location"]:
658 header = recentCallData["location"]
662 number = recentCallData["number"]
663 relTime = recentCallData["relTime"]
664 action = recentCallData["action"]
665 return contactId, header, number, relTime, action
668 def decorate_message(messageData):
669 contactId = messageData["contactId"]
670 exactTime = messageData["time"]
671 if messageData["name"]:
672 header = messageData["name"]
673 elif messageData["prettyNumber"]:
674 header = messageData["prettyNumber"]
677 number = messageData["number"]
678 relativeTime = messageData["relTime"]
680 messageParts = list(messageData["messageParts"])
681 if len(messageParts) == 0:
682 messages = ("No Transcription", )
683 elif len(messageParts) == 1:
684 messages = (messageParts[0][1], )
687 "<b>%s</b>: %s" % (messagePart[0], messagePart[1])
688 for messagePart in messageParts
691 decoratedResults = contactId, header, number, relativeTime, messages
692 return decoratedResults
695 def test_backend(username, password):
696 backend = GVoiceBackend()
697 print "Authenticated: ", backend.is_authed()
698 if not backend.is_authed():
699 print "Login?: ", backend.login(username, password)
700 print "Authenticated: ", backend.is_authed()
701 print "Is Dnd: ", backend.is_dnd()
702 #print "Setting Dnd", backend.set_dnd(True)
703 #print "Is Dnd: ", backend.is_dnd()
704 #print "Setting Dnd", backend.set_dnd(False)
705 #print "Is Dnd: ", backend.is_dnd()
707 #print "Token: ", backend._token
708 #print "Account: ", backend.get_account_number()
709 #print "Callback: ", backend.get_callback_number()
710 #print "All Callback: ",
712 #pprint.pprint(backend.get_callback_numbers())
715 #for data in backend.get_recent():
716 # pprint.pprint(data)
717 #for data in sort_messages(backend.get_recent()):
718 # pprint.pprint(decorate_recent(data))
719 #pprint.pprint(list(backend.get_recent()))
722 #for contact in backend.get_contacts():
724 # pprint.pprint(list(backend.get_contact_details(contact[0])))
727 #for message in backend.get_messages():
728 # pprint.pprint(message)
729 #for message in sort_messages(backend.get_messages()):
730 # pprint.pprint(decorate_message(message))
735 def grab_debug_info(username, password):
736 cookieFile = os.path.join(".", "raw_cookies.txt")
738 os.remove(cookieFile)
742 backend = GVoiceBackend(cookieFile)
743 browser = backend._browser
746 ("forward", backend._forwardURL),
747 ("token", backend._tokenURL),
748 ("login", backend._loginURL),
749 ("isdnd", backend._isDndURL),
750 ("contacts", backend._contactsURL),
752 ("voicemail", backend._voicemailURL),
753 ("sms", backend._smsURL),
755 ("recent", backend._recentCallsURL),
756 ("placed", backend._placedCallsURL),
757 ("recieved", backend._receivedCallsURL),
758 ("missed", backend._missedCallsURL),
762 print "Grabbing pre-login pages"
763 for name, url in _TEST_WEBPAGES:
765 page = browser.download(url)
766 except StandardError, e:
769 print "\tWriting to file"
770 with open("not_loggedin_%s.txt" % name, "w") as f:
774 print "Attempting login"
775 galxToken = backend._get_token()
776 loginSuccessOrFailurePage = backend._login(username, password, galxToken)
777 with open("loggingin.txt", "w") as f:
778 print "\tWriting to file"
779 f.write(loginSuccessOrFailurePage)
781 backend._grab_account_info(loginSuccessOrFailurePage)
783 # Retry in case the redirect failed
784 # luckily is_authed does everything we need for a retry
785 loggedIn = backend.is_authed(True)
790 print "Grabbing post-login pages"
791 for name, url in _TEST_WEBPAGES:
793 page = browser.download(url)
794 except StandardError, e:
797 print "\tWriting to file"
798 with open("loggedin_%s.txt" % name, "w") as f:
802 browser.cookies.save()
803 print "\tWriting cookies to file"
804 with open("cookies.txt", "w") as f:
806 "%s: %s\n" % (c.name, c.value)
807 for c in browser.cookies
811 if __name__ == "__main__":
813 logging.basicConfig(level=logging.DEBUG)
814 #test_backend(sys.argv[1], sys.argv[2])
815 grab_debug_info(sys.argv[1], sys.argv[2])