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")
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 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 # 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._gvDialingStrRe = re.compile("This may take a few seconds", re.M)
140 self._clicktocallURL = "https://www.google.com/voice/m/sendcall"
141 self._sendSmsURL = "https://www.google.com/voice/m/sendsms"
143 self._recentCallsURL = "https://www.google.com/voice/inbox/recent/"
144 self._placedCallsURL = "https://www.google.com/voice/inbox/recent/placed/"
145 self._receivedCallsURL = "https://www.google.com/voice/inbox/recent/received/"
146 self._missedCallsURL = "https://www.google.com/voice/inbox/recent/missed/"
148 self._contactsRe = re.compile(r"""<a href="/voice/m/contact/(\d+)">(.*?)</a>""", re.S)
149 self._contactsNextRe = re.compile(r""".*<a href="/voice/m/contacts(\?p=\d+)">Next.*?</a>""", re.S)
150 self._contactsURL = "https://www.google.com/voice/mobile/contacts"
151 self._contactDetailPhoneRe = re.compile(r"""<div.*?>([0-9+\-\(\) \t]+?)<span.*?>\((\w+)\)</span>""", re.S)
152 self._contactDetailURL = "https://www.google.com/voice/mobile/contact"
154 self._voicemailURL = "https://www.google.com/voice/inbox/recent/voicemail/"
155 self._smsURL = "https://www.google.com/voice/inbox/recent/sms/"
156 self._seperateVoicemailsRegex = re.compile(r"""^\s*<div id="(\w+)"\s* class=".*?gc-message.*?">""", re.MULTILINE | re.DOTALL)
157 self._exactVoicemailTimeRegex = re.compile(r"""<span class="gc-message-time">(.*?)</span>""", re.MULTILINE)
158 self._relativeVoicemailTimeRegex = re.compile(r"""<span class="gc-message-relative">(.*?)</span>""", re.MULTILINE)
159 self._voicemailNameRegex = re.compile(r"""<a class=.*?gc-message-name-link.*?>(.*?)</a>""", re.MULTILINE | re.DOTALL)
160 self._voicemailNumberRegex = re.compile(r"""<input type="hidden" class="gc-text gc-quickcall-ac" value="(.*?)"/>""", re.MULTILINE)
161 self._prettyVoicemailNumberRegex = re.compile(r"""<span class="gc-message-type">(.*?)</span>""", re.MULTILINE)
162 self._voicemailLocationRegex = re.compile(r"""<span class="gc-message-location">.*?<a.*?>(.*?)</a></span>""", re.MULTILINE)
163 self._messagesContactID = re.compile(r"""<a class=".*?gc-message-name-link.*?">.*?</a>\s*?<span .*?>(.*?)</span>""", re.MULTILINE)
164 self._voicemailMessageRegex = re.compile(r"""(<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>|<a .*? class="gc-message-mni">(.*?)</a>)""", re.MULTILINE)
165 self._smsFromRegex = re.compile(r"""<span class="gc-message-sms-from">(.*?)</span>""", re.MULTILINE | re.DOTALL)
166 self._smsTimeRegex = re.compile(r"""<span class="gc-message-sms-time">(.*?)</span>""", re.MULTILINE | re.DOTALL)
167 self._smsTextRegex = re.compile(r"""<span class="gc-message-sms-text">(.*?)</span>""", re.MULTILINE | re.DOTALL)
169 def is_authed(self, force = False):
171 Attempts to detect a current session
172 @note Once logged in try not to reauth more than once a minute.
173 @returns If authenticated
175 if (time.time() - self._lastAuthed) < 120 and not force:
179 page = self._browser.download(self._forwardURL)
180 self._grab_account_info(page)
182 _moduleLogger.exception(str(e))
185 self._browser.cookies.save()
186 self._lastAuthed = time.time()
189 def _get_token(self):
191 tokenPage = self._browser.download(self._tokenURL)
192 except urllib2.URLError, e:
193 _moduleLogger.exception("Translating error: %s" % str(e))
194 raise NetworkError("%s is not accesible" % self._loginURL)
195 galxTokens = self._galxRe.search(tokenPage)
196 if galxTokens is not None:
197 galxToken = galxTokens.group(1)
200 _moduleLogger.debug("Could not grab GALX token")
203 def _login(self, username, password, token):
204 loginPostData = urllib.urlencode({
207 'service': "grandcentral",
210 "PersistentCookie": "yes",
212 "continue": self._forwardURL,
216 loginSuccessOrFailurePage = self._browser.download(self._loginURL, loginPostData)
217 except urllib2.URLError, e:
218 _moduleLogger.exception("Translating error: %s" % str(e))
219 raise NetworkError("%s is not accesible" % self._loginURL)
220 return loginSuccessOrFailurePage
222 def login(self, username, password):
224 Attempt to login to GoogleVoice
225 @returns Whether login was successful or not
228 galxToken = self._get_token()
229 loginSuccessOrFailurePage = self._login(username, password, galxToken)
232 self._grab_account_info(loginSuccessOrFailurePage)
234 # Retry in case the redirect failed
235 # luckily is_authed does everything we need for a retry
236 loggedIn = self.is_authed(True)
238 _moduleLogger.exception(str(e))
240 _moduleLogger.info("Redirection failed on initial login attempt, auto-corrected for this")
242 self._browser.cookies.save()
243 self._lastAuthed = time.time()
247 self._lastAuthed = 0.0
248 self._browser.cookies.clear()
249 self._browser.cookies.save()
251 def dial(self, number):
253 This is the main function responsible for initating the callback
255 number = self._send_validation(number)
257 clickToCallData = urllib.urlencode({
259 "phone": self._callbackNumber,
260 "_rnr_se": self._token,
263 'Referer' : 'https://google.com/voice/m/callsms',
265 callSuccessPage = self._browser.download(self._clicktocallURL, clickToCallData, None, otherData)
266 except urllib2.URLError, e:
267 _moduleLogger.exception("Translating error: %s" % str(e))
268 raise NetworkError("%s is not accesible" % self._clicktocallURL)
270 if self._gvDialingStrRe.search(callSuccessPage) is None:
271 raise RuntimeError("Google Voice returned an error")
275 def send_sms(self, number, message):
276 number = self._send_validation(number)
278 smsData = urllib.urlencode({
281 "_rnr_se": self._token,
286 'Referer' : 'https://google.com/voice/m/sms',
288 smsSuccessPage = self._browser.download(self._sendSmsURL, smsData, None, otherData)
289 except urllib2.URLError, e:
290 _moduleLogger.exception("Translating error: %s" % str(e))
291 raise NetworkError("%s is not accesible" % self._sendSmsURL)
295 def is_valid_syntax(self, number):
297 @returns If This number be called ( syntax validation only )
299 return self._validateRe.match(number) is not None
301 def get_account_number(self):
303 @returns The GoogleVoice phone number
305 return self._accountNum
307 def get_callback_numbers(self):
309 @returns a dictionary mapping call back numbers to descriptions
310 @note These results are cached for 30 minutes.
312 if not self.is_authed():
314 return self._callbackNumbers
316 def set_callback_number(self, callbacknumber):
318 Set the number that GoogleVoice calls
319 @param callbacknumber should be a proper 10 digit number
321 self._callbackNumber = callbacknumber
324 def get_callback_number(self):
326 @returns Current callback number or None
328 return self._callbackNumber
330 def get_recent(self):
332 @returns Iterable of (personsName, phoneNumber, exact date, relative date, action)
335 ("Received", self._receivedCallsURL),
336 ("Missed", self._missedCallsURL),
337 ("Placed", self._placedCallsURL),
340 flatXml = self._browser.download(url)
341 except urllib2.URLError, e:
342 _moduleLogger.exception("Translating error: %s" % str(e))
343 raise NetworkError("%s is not accesible" % url)
345 allRecentHtml = self._grab_html(flatXml)
346 allRecentData = self._parse_voicemail(allRecentHtml)
347 for recentCallData in allRecentData:
348 recentCallData["action"] = action
351 def get_contacts(self):
353 @returns Iterable of (contact id, contact name)
355 contactsPagesUrls = [self._contactsURL]
356 for contactsPageUrl in contactsPagesUrls:
358 contactsPage = self._browser.download(contactsPageUrl)
359 except urllib2.URLError, e:
360 _moduleLogger.exception("Translating error: %s" % str(e))
361 raise NetworkError("%s is not accesible" % contactsPageUrl)
362 for contact_match in self._contactsRe.finditer(contactsPage):
363 contactId = contact_match.group(1)
364 contactName = saxutils.unescape(contact_match.group(2))
365 contact = contactId, contactName
368 next_match = self._contactsNextRe.match(contactsPage)
369 if next_match is not None:
370 newContactsPageUrl = self._contactsURL + next_match.group(1)
371 contactsPagesUrls.append(newContactsPageUrl)
373 def get_contact_details(self, contactId):
375 @returns Iterable of (Phone Type, Phone Number)
378 detailPage = self._browser.download(self._contactDetailURL + '/' + contactId)
379 except urllib2.URLError, e:
380 _moduleLogger.exception("Translating error: %s" % str(e))
381 raise NetworkError("%s is not accesible" % self._contactDetailURL)
383 for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
384 phoneNumber = detail_match.group(1)
385 phoneType = saxutils.unescape(detail_match.group(2))
386 yield (phoneType, phoneNumber)
388 def get_messages(self):
390 voicemailPage = self._browser.download(self._voicemailURL)
391 except urllib2.URLError, e:
392 _moduleLogger.exception("Translating error: %s" % str(e))
393 raise NetworkError("%s is not accesible" % self._voicemailURL)
394 voicemailHtml = self._grab_html(voicemailPage)
395 voicemailJson = self._grab_json(voicemailPage)
396 parsedVoicemail = self._parse_voicemail(voicemailHtml)
397 voicemails = self._merge_messages(parsedVoicemail, voicemailJson)
398 decoratedVoicemails = self._decorate_voicemail(voicemails)
401 smsPage = self._browser.download(self._smsURL)
402 except urllib2.URLError, e:
403 _moduleLogger.exception("Translating error: %s" % str(e))
404 raise NetworkError("%s is not accesible" % self._smsURL)
405 smsHtml = self._grab_html(smsPage)
406 smsJson = self._grab_json(smsPage)
407 parsedSms = self._parse_sms(smsHtml)
408 smss = self._merge_messages(parsedSms, smsJson)
409 decoratedSms = self._decorate_sms(smss)
411 allMessages = itertools.chain(decoratedVoicemails, decoratedSms)
414 def clear_caches(self):
417 def get_addressbooks(self):
419 @returns Iterable of (Address Book Factory, Book Id, Book Name)
423 def open_addressbook(self, bookId):
427 def contact_source_short_name(contactId):
432 return "Google Voice"
434 def _grab_json(self, flatXml):
435 xmlTree = ElementTree.fromstring(flatXml)
436 jsonElement = xmlTree.getchildren()[0]
437 flatJson = jsonElement.text
438 jsonTree = parse_json(flatJson)
441 def _grab_html(self, flatXml):
442 xmlTree = ElementTree.fromstring(flatXml)
443 htmlElement = xmlTree.getchildren()[1]
444 flatHtml = htmlElement.text
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
479 def _interpret_voicemail_regex(group):
480 quality, content, number = group.group(2), group.group(3), group.group(4)
481 if quality is not None and content is not None:
482 return quality, content
483 elif number is not None:
484 return "high", number
486 def _parse_voicemail(self, voicemailHtml):
487 splitVoicemail = self._seperateVoicemailsRegex.split(voicemailHtml)
488 for messageId, messageHtml in itergroup(splitVoicemail[1:], 2):
489 exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
490 exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
491 exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p")
492 relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
493 relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
494 locationGroup = self._voicemailLocationRegex.search(messageHtml)
495 location = locationGroup.group(1).strip() if locationGroup else ""
497 nameGroup = self._voicemailNameRegex.search(messageHtml)
498 name = nameGroup.group(1).strip() if nameGroup else ""
499 numberGroup = self._voicemailNumberRegex.search(messageHtml)
500 number = numberGroup.group(1).strip() if numberGroup else ""
501 prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml)
502 prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
503 contactIdGroup = self._messagesContactID.search(messageHtml)
504 contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
506 messageGroups = self._voicemailMessageRegex.finditer(messageHtml)
508 self._interpret_voicemail_regex(group)
509 for group in messageGroups
510 ) if messageGroups else ()
513 "id": messageId.strip(),
514 "contactId": contactId,
517 "relTime": relativeTime,
518 "prettyNumber": prettyNumber,
520 "location": location,
521 "messageParts": messageParts,
525 def _decorate_voicemail(self, parsedVoicemails):
526 messagePartFormat = {
531 for voicemailData in parsedVoicemails:
533 messagePartFormat[quality] % part
534 for (quality, part) in voicemailData["messageParts"]
537 message = "No Transcription"
538 whoFrom = voicemailData["name"]
539 when = voicemailData["time"]
540 voicemailData["messageParts"] = ((whoFrom, message, when), )
543 def _parse_sms(self, smsHtml):
544 splitSms = self._seperateVoicemailsRegex.split(smsHtml)
545 for messageId, messageHtml in itergroup(splitSms[1:], 2):
546 exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
547 exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
548 exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p")
549 relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
550 relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
552 nameGroup = self._voicemailNameRegex.search(messageHtml)
553 name = nameGroup.group(1).strip() if nameGroup else ""
554 numberGroup = self._voicemailNumberRegex.search(messageHtml)
555 number = numberGroup.group(1).strip() if numberGroup else ""
556 prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml)
557 prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
558 contactIdGroup = self._messagesContactID.search(messageHtml)
559 contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
561 fromGroups = self._smsFromRegex.finditer(messageHtml)
562 fromParts = (group.group(1).strip() for group in fromGroups)
563 textGroups = self._smsTextRegex.finditer(messageHtml)
564 textParts = (group.group(1).strip() for group in textGroups)
565 timeGroups = self._smsTimeRegex.finditer(messageHtml)
566 timeParts = (group.group(1).strip() for group in timeGroups)
568 messageParts = itertools.izip(fromParts, textParts, timeParts)
571 "id": messageId.strip(),
572 "contactId": contactId,
575 "relTime": relativeTime,
576 "prettyNumber": prettyNumber,
579 "messageParts": messageParts,
583 def _decorate_sms(self, parsedTexts):
587 def _merge_messages(parsedMessages, json):
588 for message in parsedMessages:
590 jsonItem = json["messages"][id]
591 message["isRead"] = jsonItem["isRead"]
592 message["isSpam"] = jsonItem["isSpam"]
593 message["isTrash"] = jsonItem["isTrash"]
594 message["isArchived"] = "inbox" not in jsonItem["labels"]
598 def set_sane_callback(backend):
600 Try to set a sane default callback number on these preferences
601 1) 1747 numbers ( Gizmo )
602 2) anything with gizmo in the name
603 3) anything with computer in the name
606 numbers = backend.get_callback_numbers()
608 priorityOrderedCriteria = [
616 for numberCriteria, descriptionCriteria in priorityOrderedCriteria:
617 for number, description in numbers.iteritems():
618 if numberCriteria is not None and re.compile(numberCriteria).match(number) is None:
620 if descriptionCriteria is not None and re.compile(descriptionCriteria).match(description) is None:
622 backend.set_callback_number(number)
626 def sort_messages(allMessages):
627 sortableAllMessages = [
628 (message["time"], message)
629 for message in allMessages
631 sortableAllMessages.sort(reverse=True)
634 for (exactTime, message) in sortableAllMessages
638 def decorate_recent(recentCallData):
640 @returns (personsName, phoneNumber, date, action)
642 contactId = recentCallData["contactId"]
643 if recentCallData["name"]:
644 header = recentCallData["name"]
645 elif recentCallData["prettyNumber"]:
646 header = recentCallData["prettyNumber"]
647 elif recentCallData["location"]:
648 header = recentCallData["location"]
652 number = recentCallData["number"]
653 relTime = recentCallData["relTime"]
654 action = recentCallData["action"]
655 return contactId, header, number, relTime, action
658 def decorate_message(messageData):
659 contactId = messageData["contactId"]
660 exactTime = messageData["time"]
661 if messageData["name"]:
662 header = messageData["name"]
663 elif messageData["prettyNumber"]:
664 header = messageData["prettyNumber"]
667 number = messageData["number"]
668 relativeTime = messageData["relTime"]
670 messageParts = list(messageData["messageParts"])
671 if len(messageParts) == 0:
672 messages = ("No Transcription", )
673 elif len(messageParts) == 1:
674 messages = (messageParts[0][1], )
677 "<b>%s</b>: %s" % (messagePart[0], messagePart[1])
678 for messagePart in messageParts
681 decoratedResults = contactId, header, number, relativeTime, messages
682 return decoratedResults
685 def test_backend(username, password):
687 print "Authenticated: ", backend.is_authed()
688 if not backend.is_authed():
689 print "Login?: ", backend.login(username, password)
690 print "Authenticated: ", backend.is_authed()
692 print "Token: ", backend._token
693 #print "Account: ", backend.get_account_number()
694 #print "Callback: ", backend.get_callback_number()
695 #print "All Callback: ",
697 #pprint.pprint(backend.get_callback_numbers())
700 #for data in backend.get_recent():
701 # pprint.pprint(data)
702 #for data in sort_messages(backend.get_recent()):
703 # pprint.pprint(decorate_recent(data))
704 #pprint.pprint(list(backend.get_recent()))
707 #for contact in backend.get_contacts():
709 # pprint.pprint(list(backend.get_contact_details(contact[0])))
712 for message in backend.get_messages():
713 message["messageParts"] = list(message["messageParts"])
714 pprint.pprint(message)
715 #for message in sort_messages(backend.get_messages()):
716 # pprint.pprint(decorate_message(message))
721 def grab_debug_info(username, password):
722 cookieFile = os.path.join(".", "raw_cookies.txt")
724 os.remove(cookieFile)
728 backend = GVDialer(cookieFile)
729 browser = backend._browser
732 ("forward", backend._forwardURL),
733 ("token", backend._tokenURL),
734 ("login", backend._loginURL),
735 ("contacts", backend._contactsURL),
737 ("voicemail", backend._voicemailURL),
738 ("sms", backend._smsURL),
740 ("recent", backend._recentCallsURL),
741 ("placed", backend._placedCallsURL),
742 ("recieved", backend._receivedCallsURL),
743 ("missed", backend._missedCallsURL),
747 print "Grabbing pre-login pages"
748 for name, url in _TEST_WEBPAGES:
750 page = browser.download(url)
751 except StandardError, e:
754 print "\tWriting to file"
755 with open("not_loggedin_%s.txt" % name, "w") as f:
759 print "Attempting login"
760 galxToken = backend._get_token()
761 loginSuccessOrFailurePage = backend._login(username, password, galxToken)
762 with open("loggingin.txt", "w") as f:
763 print "\tWriting to file"
764 f.write(loginSuccessOrFailurePage)
766 backend._grab_account_info(loginSuccessOrFailurePage)
768 # Retry in case the redirect failed
769 # luckily is_authed does everything we need for a retry
770 loggedIn = backend.is_authed(True)
775 print "Grabbing post-login pages"
776 for name, url in _TEST_WEBPAGES:
778 page = browser.download(url)
779 except StandardError, e:
782 print "\tWriting to file"
783 with open("loggedin_%s.txt" % name, "w") as f:
787 browser.cookies.save()
788 print "\tWriting cookies to file"
789 with open("cookies.txt", "w") as f:
791 "%s: %s\n" % (c.name, c.value)
792 for c in browser.cookies
796 if __name__ == "__main__":
798 logging.basicConfig(level=logging.DEBUG)
799 #test_backend(sys.argv[1], sys.argv[2])
800 grab_debug_info(sys.argv[1], sys.argv[2])