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.dialer")
51 _TRUE_REGEX = re.compile("true")
52 _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 GVDialer(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 _forwardURL = "https://www.google.com/voice/mobile/phones"
129 def is_authed(self, force = False):
131 Attempts to detect a current session
132 @note Once logged in try not to reauth more than once a minute.
133 @returns If authenticated
135 if (time.time() - self._lastAuthed) < 120 and not force:
139 page = self._browser.download(self._forwardURL)
140 self._grab_account_info(page)
142 _moduleLogger.exception(str(e))
145 self._browser.cookies.save()
146 self._lastAuthed = time.time()
149 _tokenURL = "http://www.google.com/voice/m"
150 _loginURL = "https://www.google.com/accounts/ServiceLoginAuth"
151 _galxRe = re.compile(r"""<input.*?name="GALX".*?value="(.*?)".*?/>""", re.MULTILINE | re.DOTALL)
153 def _get_token(self):
155 tokenPage = self._browser.download(self._tokenURL)
156 except urllib2.URLError, e:
157 _moduleLogger.exception("Translating error: %s" % str(e))
158 raise NetworkError("%s is not accesible" % self._loginURL)
159 galxTokens = self._galxRe.search(tokenPage)
160 if galxTokens is not None:
161 galxToken = galxTokens.group(1)
164 _moduleLogger.debug("Could not grab GALX token")
167 def _login(self, username, password, token):
168 loginPostData = urllib.urlencode({
171 'service': "grandcentral",
174 "PersistentCookie": "yes",
176 "continue": self._forwardURL,
180 loginSuccessOrFailurePage = self._browser.download(self._loginURL, loginPostData)
181 except urllib2.URLError, e:
182 _moduleLogger.exception("Translating error: %s" % str(e))
183 raise NetworkError("%s is not accesible" % self._loginURL)
184 return loginSuccessOrFailurePage
186 def login(self, username, password):
188 Attempt to login to GoogleVoice
189 @returns Whether login was successful or not
192 galxToken = self._get_token()
193 loginSuccessOrFailurePage = self._login(username, password, galxToken)
196 self._grab_account_info(loginSuccessOrFailurePage)
198 # Retry in case the redirect failed
199 # luckily is_authed does everything we need for a retry
200 loggedIn = self.is_authed(True)
202 _moduleLogger.exception(str(e))
204 _moduleLogger.info("Redirection failed on initial login attempt, auto-corrected for this")
206 self._browser.cookies.save()
207 self._lastAuthed = time.time()
211 self._lastAuthed = 0.0
212 self._browser.cookies.clear()
213 self._browser.cookies.save()
215 _gvDialingStrRe = re.compile("This may take a few seconds", re.M)
216 _clicktocallURL = "https://www.google.com/voice/m/sendcall"
218 def dial(self, number):
220 This is the main function responsible for initating the callback
222 number = self._send_validation(number)
224 clickToCallData = urllib.urlencode({
226 "phone": self._callbackNumber,
227 "_rnr_se": self._token,
230 'Referer' : 'https://google.com/voice/m/callsms',
232 callSuccessPage = self._browser.download(self._clicktocallURL, clickToCallData, None, otherData)
233 except urllib2.URLError, e:
234 _moduleLogger.exception("Translating error: %s" % str(e))
235 raise NetworkError("%s is not accesible" % self._clicktocallURL)
237 if self._gvDialingStrRe.search(callSuccessPage) is None:
238 raise RuntimeError("Google Voice returned an error")
242 _sendSmsURL = "https://www.google.com/voice/m/sendsms"
244 def send_sms(self, number, message):
245 number = self._send_validation(number)
247 smsData = urllib.urlencode({
250 "_rnr_se": self._token,
255 'Referer' : 'https://google.com/voice/m/sms',
257 smsSuccessPage = self._browser.download(self._sendSmsURL, smsData, None, otherData)
258 except urllib2.URLError, e:
259 _moduleLogger.exception("Translating error: %s" % str(e))
260 raise NetworkError("%s is not accesible" % self._sendSmsURL)
264 _validateRe = re.compile("^[0-9]{10,}$")
266 def is_valid_syntax(self, number):
268 @returns If This number be called ( syntax validation only )
270 return self._validateRe.match(number) is not None
272 def get_account_number(self):
274 @returns The GoogleVoice phone number
276 return self._accountNum
278 def get_callback_numbers(self):
280 @returns a dictionary mapping call back numbers to descriptions
281 @note These results are cached for 30 minutes.
283 if not self.is_authed():
285 return self._callbackNumbers
287 def set_callback_number(self, callbacknumber):
289 Set the number that GoogleVoice calls
290 @param callbacknumber should be a proper 10 digit number
292 self._callbackNumber = callbacknumber
295 def get_callback_number(self):
297 @returns Current callback number or None
299 return self._callbackNumber
301 _recentCallsURL = "https://www.google.com/voice/inbox/recent/"
302 _placedCallsURL = "https://www.google.com/voice/inbox/recent/placed/"
303 _receivedCallsURL = "https://www.google.com/voice/inbox/recent/received/"
304 _missedCallsURL = "https://www.google.com/voice/inbox/recent/missed/"
306 def get_recent(self):
308 @returns Iterable of (personsName, phoneNumber, exact date, relative date, action)
311 ("Received", self._receivedCallsURL),
312 ("Missed", self._missedCallsURL),
313 ("Placed", self._placedCallsURL),
316 flatXml = self._browser.download(url)
317 except urllib2.URLError, e:
318 _moduleLogger.exception("Translating error: %s" % str(e))
319 raise NetworkError("%s is not accesible" % url)
321 allRecentHtml = self._grab_html(flatXml)
322 allRecentData = self._parse_voicemail(allRecentHtml)
323 for recentCallData in allRecentData:
324 recentCallData["action"] = action
327 _contactsRe = re.compile(r"""<a href="/voice/m/contact/(\d+)">(.*?)</a>""", re.S)
328 _contactsNextRe = re.compile(r""".*<a href="/voice/m/contacts(\?p=\d+)">Next.*?</a>""", re.S)
329 _contactsURL = "https://www.google.com/voice/mobile/contacts"
331 def get_contacts(self):
333 @returns Iterable of (contact id, contact name)
335 contactsPagesUrls = [self._contactsURL]
336 for contactsPageUrl in contactsPagesUrls:
338 contactsPage = self._browser.download(contactsPageUrl)
339 except urllib2.URLError, e:
340 _moduleLogger.exception("Translating error: %s" % str(e))
341 raise NetworkError("%s is not accesible" % contactsPageUrl)
342 for contact_match in self._contactsRe.finditer(contactsPage):
343 contactId = contact_match.group(1)
344 contactName = saxutils.unescape(contact_match.group(2))
345 contact = contactId, contactName
348 next_match = self._contactsNextRe.match(contactsPage)
349 if next_match is not None:
350 newContactsPageUrl = self._contactsURL + next_match.group(1)
351 contactsPagesUrls.append(newContactsPageUrl)
353 _contactDetailPhoneRe = re.compile(r"""<div.*?>([0-9+\-\(\) \t]+?)<span.*?>\((\w+)\)</span>""", re.S)
354 _contactDetailURL = "https://www.google.com/voice/mobile/contact"
356 def get_contact_details(self, contactId):
358 @returns Iterable of (Phone Type, Phone Number)
361 detailPage = self._browser.download(self._contactDetailURL + '/' + contactId)
362 except urllib2.URLError, e:
363 _moduleLogger.exception("Translating error: %s" % str(e))
364 raise NetworkError("%s is not accesible" % self._contactDetailURL)
366 for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
367 phoneNumber = detail_match.group(1)
368 phoneType = saxutils.unescape(detail_match.group(2))
369 yield (phoneType, phoneNumber)
371 _voicemailURL = "https://www.google.com/voice/inbox/recent/voicemail/"
372 _smsURL = "https://www.google.com/voice/inbox/recent/sms/"
375 def _merge_messages(parsedMessages, json):
376 for message in parsedMessages:
378 jsonItem = json["messages"][id]
379 message["isRead"] = jsonItem["isRead"]
380 message["isSpam"] = jsonItem["isSpam"]
381 message["isTrash"] = jsonItem["isTrash"]
384 def get_messages(self):
386 voicemailPage = self._browser.download(self._voicemailURL)
387 except urllib2.URLError, e:
388 _moduleLogger.exception("Translating error: %s" % str(e))
389 raise NetworkError("%s is not accesible" % self._voicemailURL)
390 voicemailHtml = self._grab_html(voicemailPage)
391 voicemailJson = self._grab_json(voicemailPage)
392 parsedVoicemail = self._parse_voicemail(voicemailHtml)
393 voicemails = self._merge_messages(parsedVoicemail, voicemailJson)
394 decoratedVoicemails = self._decorate_voicemail(voicemails)
397 smsPage = self._browser.download(self._smsURL)
398 except urllib2.URLError, e:
399 _moduleLogger.exception("Translating error: %s" % str(e))
400 raise NetworkError("%s is not accesible" % self._smsURL)
401 smsHtml = self._grab_html(smsPage)
402 smsJson = self._grab_json(smsPage)
403 parsedSms = self._parse_sms(smsHtml)
404 smss = self._merge_messages(parsedSms, smsJson)
405 decoratedSms = self._decorate_sms(smss)
407 allMessages = itertools.chain(decoratedVoicemails, decoratedSms)
410 def clear_caches(self):
413 def get_addressbooks(self):
415 @returns Iterable of (Address Book Factory, Book Id, Book Name)
419 def open_addressbook(self, bookId):
423 def contact_source_short_name(contactId):
428 return "Google Voice"
430 def _grab_json(self, flatXml):
431 xmlTree = ElementTree.fromstring(flatXml)
432 jsonElement = xmlTree.getchildren()[0]
433 flatJson = jsonElement.text
434 jsonTree = parse_json(flatJson)
437 def _grab_html(self, flatXml):
438 xmlTree = ElementTree.fromstring(flatXml)
439 htmlElement = xmlTree.getchildren()[1]
440 flatHtml = htmlElement.text
443 _tokenRe = re.compile(r"""<input.*?name="_rnr_se".*?value="(.*?)"\s*/>""")
444 _accountNumRe = re.compile(r"""<b class="ms\d">(.{14})</b></div>""")
445 _callbackRe = re.compile(r"""\s+(.*?):\s*(.*?)<br\s*/>\s*$""", re.M)
447 def _grab_account_info(self, page):
448 tokenGroup = self._tokenRe.search(page)
449 if tokenGroup is None:
450 raise RuntimeError("Could not extract authentication token from GoogleVoice")
451 self._token = tokenGroup.group(1)
453 anGroup = self._accountNumRe.search(page)
454 if anGroup is not None:
455 self._accountNum = anGroup.group(1)
457 _moduleLogger.debug("Could not extract account number from GoogleVoice")
459 self._callbackNumbers = {}
460 for match in self._callbackRe.finditer(page):
461 callbackNumber = match.group(2)
462 callbackName = match.group(1)
463 self._callbackNumbers[callbackNumber] = callbackName
464 if len(self._callbackNumbers) == 0:
465 _moduleLogger.debug("Could not extract callback numbers from GoogleVoice (the troublesome page follows):\n%s" % page)
467 def _send_validation(self, number):
468 if not self.is_valid_syntax(number):
469 raise ValueError('Number is not valid: "%s"' % number)
470 elif not self.is_authed():
471 raise RuntimeError("Not Authenticated")
473 if len(number) == 11 and number[0] == 1:
474 # Strip leading 1 from 11 digit dialing
478 _seperateVoicemailsRegex = re.compile(r"""^\s*<div id="(\w+)"\s* class=".*?gc-message.*?">""", re.MULTILINE | re.DOTALL)
479 _exactVoicemailTimeRegex = re.compile(r"""<span class="gc-message-time">(.*?)</span>""", re.MULTILINE)
480 _relativeVoicemailTimeRegex = re.compile(r"""<span class="gc-message-relative">(.*?)</span>""", re.MULTILINE)
481 _voicemailNameRegex = re.compile(r"""<a class=.*?gc-message-name-link.*?>(.*?)</a>""", re.MULTILINE | re.DOTALL)
482 _voicemailNumberRegex = re.compile(r"""<input type="hidden" class="gc-text gc-quickcall-ac" value="(.*?)"/>""", re.MULTILINE)
483 _prettyVoicemailNumberRegex = re.compile(r"""<span class="gc-message-type">(.*?)</span>""", re.MULTILINE)
484 _voicemailLocationRegex = re.compile(r"""<span class="gc-message-location">.*?<a.*?>(.*?)</a></span>""", re.MULTILINE)
485 _messagesContactID = re.compile(r"""<a class=".*?gc-message-name-link.*?">.*?</a>\s*?<span .*?>(.*?)</span>""", re.MULTILINE)
486 #_voicemailMessageRegex = re.compile(r"""<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>""", re.MULTILINE)
487 #_voicemailMessageRegex = re.compile(r"""<a .*? class="gc-message-mni">(.*?)</a>""", re.MULTILINE)
488 _voicemailMessageRegex = re.compile(r"""(<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>|<a .*? class="gc-message-mni">(.*?)</a>)""", re.MULTILINE)
491 def _interpret_voicemail_regex(group):
492 quality, content, number = group.group(2), group.group(3), group.group(4)
493 if quality is not None and content is not None:
494 return quality, content
495 elif number is not None:
496 return "high", number
498 def _parse_voicemail(self, voicemailHtml):
499 splitVoicemail = self._seperateVoicemailsRegex.split(voicemailHtml)
500 for messageId, messageHtml in itergroup(splitVoicemail[1:], 2):
501 exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
502 exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
503 exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p")
504 relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
505 relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
506 locationGroup = self._voicemailLocationRegex.search(messageHtml)
507 location = locationGroup.group(1).strip() if locationGroup else ""
509 nameGroup = self._voicemailNameRegex.search(messageHtml)
510 name = nameGroup.group(1).strip() if nameGroup else ""
511 numberGroup = self._voicemailNumberRegex.search(messageHtml)
512 number = numberGroup.group(1).strip() if numberGroup else ""
513 prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml)
514 prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
515 contactIdGroup = self._messagesContactID.search(messageHtml)
516 contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
518 messageGroups = self._voicemailMessageRegex.finditer(messageHtml)
520 self._interpret_voicemail_regex(group)
521 for group in messageGroups
522 ) if messageGroups else ()
525 "id": messageId.strip(),
526 "contactId": contactId,
529 "relTime": relativeTime,
530 "prettyNumber": prettyNumber,
532 "location": location,
533 "messageParts": messageParts,
537 def _decorate_voicemail(self, parsedVoicemails):
538 messagePartFormat = {
543 for voicemailData in parsedVoicemails:
545 messagePartFormat[quality] % part
546 for (quality, part) in voicemailData["messageParts"]
549 message = "No Transcription"
550 whoFrom = voicemailData["name"]
551 when = voicemailData["time"]
552 voicemailData["messageParts"] = ((whoFrom, message, when), )
555 _smsFromRegex = re.compile(r"""<span class="gc-message-sms-from">(.*?)</span>""", re.MULTILINE | re.DOTALL)
556 _smsTimeRegex = re.compile(r"""<span class="gc-message-sms-time">(.*?)</span>""", re.MULTILINE | re.DOTALL)
557 _smsTextRegex = re.compile(r"""<span class="gc-message-sms-text">(.*?)</span>""", re.MULTILINE | re.DOTALL)
559 def _parse_sms(self, smsHtml):
560 splitSms = self._seperateVoicemailsRegex.split(smsHtml)
561 for messageId, messageHtml in itergroup(splitSms[1:], 2):
562 exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
563 exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
564 exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p")
565 relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
566 relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
568 nameGroup = self._voicemailNameRegex.search(messageHtml)
569 name = nameGroup.group(1).strip() if nameGroup else ""
570 numberGroup = self._voicemailNumberRegex.search(messageHtml)
571 number = numberGroup.group(1).strip() if numberGroup else ""
572 prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml)
573 prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
574 contactIdGroup = self._messagesContactID.search(messageHtml)
575 contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
577 fromGroups = self._smsFromRegex.finditer(messageHtml)
578 fromParts = (group.group(1).strip() for group in fromGroups)
579 textGroups = self._smsTextRegex.finditer(messageHtml)
580 textParts = (group.group(1).strip() for group in textGroups)
581 timeGroups = self._smsTimeRegex.finditer(messageHtml)
582 timeParts = (group.group(1).strip() for group in timeGroups)
584 messageParts = itertools.izip(fromParts, textParts, timeParts)
587 "id": messageId.strip(),
588 "contactId": contactId,
591 "relTime": relativeTime,
592 "prettyNumber": prettyNumber,
595 "messageParts": messageParts,
599 def _decorate_sms(self, parsedTexts):
603 def set_sane_callback(backend):
605 Try to set a sane default callback number on these preferences
606 1) 1747 numbers ( Gizmo )
607 2) anything with gizmo in the name
608 3) anything with computer in the name
611 numbers = backend.get_callback_numbers()
613 priorityOrderedCriteria = [
621 for numberCriteria, descriptionCriteria in priorityOrderedCriteria:
622 for number, description in numbers.iteritems():
623 if numberCriteria is not None and re.compile(numberCriteria).match(number) is None:
625 if descriptionCriteria is not None and re.compile(descriptionCriteria).match(description) is None:
627 backend.set_callback_number(number)
631 def sort_messages(allMessages):
632 sortableAllMessages = [
633 (message["time"], message)
634 for message in allMessages
636 sortableAllMessages.sort(reverse=True)
639 for (exactTime, message) in sortableAllMessages
643 def decorate_recent(recentCallData):
645 @returns (personsName, phoneNumber, date, action)
647 contactId = recentCallData["contactId"]
648 if recentCallData["name"]:
649 header = recentCallData["name"]
650 elif recentCallData["prettyNumber"]:
651 header = recentCallData["prettyNumber"]
652 elif recentCallData["location"]:
653 header = recentCallData["location"]
657 number = recentCallData["number"]
658 relTime = recentCallData["relTime"]
659 action = recentCallData["action"]
660 return contactId, header, number, relTime, action
663 def decorate_message(messageData):
664 contactId = messageData["contactId"]
665 exactTime = messageData["time"]
666 if messageData["name"]:
667 header = messageData["name"]
668 elif messageData["prettyNumber"]:
669 header = messageData["prettyNumber"]
672 number = messageData["number"]
673 relativeTime = messageData["relTime"]
675 messageParts = list(messageData["messageParts"])
676 if len(messageParts) == 0:
677 messages = ("No Transcription", )
678 elif len(messageParts) == 1:
679 messages = (messageParts[0][1], )
682 "<b>%s</b>: %s" % (messagePart[0], messagePart[1])
683 for messagePart in messageParts
686 decoratedResults = contactId, header, number, relativeTime, messages
687 return decoratedResults
690 def test_backend(username, password):
692 print "Authenticated: ", backend.is_authed()
693 if not backend.is_authed():
694 print "Login?: ", backend.login(username, password)
695 print "Authenticated: ", backend.is_authed()
697 print "Token: ", backend._token
698 #print "Account: ", backend.get_account_number()
699 #print "Callback: ", backend.get_callback_number()
700 #print "All Callback: ",
702 #pprint.pprint(backend.get_callback_numbers())
705 #for data in backend.get_recent():
706 # pprint.pprint(data)
707 #for data in sort_messages(backend.get_recent()):
708 # pprint.pprint(decorate_recent(data))
709 #pprint.pprint(list(backend.get_recent()))
712 #for contact in backend.get_contacts():
714 # pprint.pprint(list(backend.get_contact_details(contact[0])))
717 for message in backend.get_messages():
718 message["messageParts"] = list(message["messageParts"])
719 pprint.pprint(message)
720 #for message in sort_messages(backend.get_messages()):
721 # pprint.pprint(decorate_message(message))
727 ("forward", GVDialer._forwardURL),
728 ("token", GVDialer._tokenURL),
729 ("login", GVDialer._loginURL),
730 ("contacts", GVDialer._contactsURL),
732 ("voicemail", GVDialer._voicemailURL),
733 ("sms", GVDialer._smsURL),
735 ("recent", GVDialer._recentCallsURL),
736 ("placed", GVDialer._placedCallsURL),
737 ("recieved", GVDialer._receivedCallsURL),
738 ("missed", GVDialer._missedCallsURL),
742 def grab_debug_info(username, password):
743 cookieFile = os.path.join(".", "raw_cookies.txt")
745 os.remove(cookieFile)
749 backend = GVDialer(cookieFile)
750 browser = backend._browser
753 print "Grabbing pre-login pages"
754 for name, url in _TEST_WEBPAGES:
756 page = browser.download(url)
757 except StandardError, e:
760 print "\tWriting to file"
761 with open("not_loggedin_%s.txt" % name, "w") as f:
765 print "Attempting login"
766 galxToken = backend._get_token()
767 loginSuccessOrFailurePage = backend._login(username, password, galxToken)
768 with open("loggingin.txt", "w") as f:
769 print "\tWriting to file"
770 f.write(loginSuccessOrFailurePage)
772 backend._grab_account_info(loginSuccessOrFailurePage)
774 # Retry in case the redirect failed
775 # luckily is_authed does everything we need for a retry
776 loggedIn = backend.is_authed(True)
781 print "Grabbing post-login pages"
782 for name, url in _TEST_WEBPAGES:
784 page = browser.download(url)
785 except StandardError, e:
788 print "\tWriting to file"
789 with open("loggedin_%s.txt" % name, "w") as f:
793 browser.cookies.save()
794 print "\tWriting cookies to file"
795 with open("cookies.txt", "w") as f:
797 "%s: %s\n" % (c.name, c.value)
798 for c in browser.cookies
802 if __name__ == "__main__":
804 logging.basicConfig(level=logging.DEBUG)
805 #test_backend(sys.argv[1], sys.argv[2])
806 grab_debug_info(sys.argv[1], sys.argv[2])