http://posttopic.com/topic/google-voice-add-on-development
"""
+from __future__ import with_statement
import os
import re
from xml.etree import ElementTree
-import browser_emu
-
try:
import simplejson
except ImportError:
simplejson = None
+import browser_emu
+
_moduleLogger = logging.getLogger("gvoice.backend")
-_TRUE_REGEX = re.compile("true")
-_FALSE_REGEX = re.compile("false")
def safe_eval(s):
+ _TRUE_REGEX = re.compile("true")
+ _FALSE_REGEX = re.compile("false")
s = _TRUE_REGEX.sub("True", s)
s = _FALSE_REGEX.sub("False", s)
return eval(s, {}, {})
self._callbackNumber = ""
self._callbackNumbers = {}
+ # Suprisingly, moving all of these from class to self sped up startup time
+
+ self._validateRe = re.compile("^[0-9]{10,}$")
+
+ self._forwardURL = "https://www.google.com/voice/mobile/phones"
+ self._tokenURL = "http://www.google.com/voice/m"
+ self._loginURL = "https://www.google.com/accounts/ServiceLoginAuth"
+ self._galxRe = re.compile(r"""<input.*?name="GALX".*?value="(.*?)".*?/>""", re.MULTILINE | re.DOTALL)
+ self._tokenRe = re.compile(r"""<input.*?name="_rnr_se".*?value="(.*?)"\s*/>""")
+ self._accountNumRe = re.compile(r"""<b class="ms\d">(.{14})</b></div>""")
+ self._callbackRe = re.compile(r"""\s+(.*?):\s*(.*?)<br\s*/>\s*$""", re.M)
+
+ self._gvDialingStrRe = re.compile("This may take a few seconds", re.M)
+ self._clicktocallURL = "https://www.google.com/voice/m/sendcall"
+ self._sendSmsURL = "https://www.google.com/voice/m/sendsms"
+
+ self._recentCallsURL = "https://www.google.com/voice/inbox/recent/"
+ self._placedCallsURL = "https://www.google.com/voice/inbox/recent/placed/"
+ self._receivedCallsURL = "https://www.google.com/voice/inbox/recent/received/"
+ self._missedCallsURL = "https://www.google.com/voice/inbox/recent/missed/"
+
+ self._contactsRe = re.compile(r"""<a href="/voice/m/contact/(\d+)">(.*?)</a>""", re.S)
+ self._contactsNextRe = re.compile(r""".*<a href="/voice/m/contacts(\?p=\d+)">Next.*?</a>""", re.S)
+ self._contactsURL = "https://www.google.com/voice/mobile/contacts"
+ self._contactDetailPhoneRe = re.compile(r"""<div.*?>([0-9+\-\(\) \t]+?)<span.*?>\((\w+)\)</span>""", re.S)
+ self._contactDetailURL = "https://www.google.com/voice/mobile/contact"
+
+ self._voicemailURL = "https://www.google.com/voice/inbox/recent/voicemail/"
+ self._smsURL = "https://www.google.com/voice/inbox/recent/sms/"
+ self._seperateVoicemailsRegex = re.compile(r"""^\s*<div id="(\w+)"\s* class=".*?gc-message.*?">""", re.MULTILINE | re.DOTALL)
+ self._exactVoicemailTimeRegex = re.compile(r"""<span class="gc-message-time">(.*?)</span>""", re.MULTILINE)
+ self._relativeVoicemailTimeRegex = re.compile(r"""<span class="gc-message-relative">(.*?)</span>""", re.MULTILINE)
+ self._voicemailNameRegex = re.compile(r"""<a class=.*?gc-message-name-link.*?>(.*?)</a>""", re.MULTILINE | re.DOTALL)
+ self._voicemailNumberRegex = re.compile(r"""<input type="hidden" class="gc-text gc-quickcall-ac" value="(.*?)"/>""", re.MULTILINE)
+ self._prettyVoicemailNumberRegex = re.compile(r"""<span class="gc-message-type">(.*?)</span>""", re.MULTILINE)
+ self._voicemailLocationRegex = re.compile(r"""<span class="gc-message-location">.*?<a.*?>(.*?)</a></span>""", re.MULTILINE)
+ self._messagesContactID = re.compile(r"""<a class=".*?gc-message-name-link.*?">.*?</a>\s*?<span .*?>(.*?)</span>""", re.MULTILINE)
+ self._voicemailMessageRegex = re.compile(r"""(<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>|<a .*? class="gc-message-mni">(.*?)</a>)""", re.MULTILINE)
+ self._smsFromRegex = re.compile(r"""<span class="gc-message-sms-from">(.*?)</span>""", re.MULTILINE | re.DOTALL)
+ self._smsTimeRegex = re.compile(r"""<span class="gc-message-sms-time">(.*?)</span>""", re.MULTILINE | re.DOTALL)
+ self._smsTextRegex = re.compile(r"""<span class="gc-message-sms-text">(.*?)</span>""", re.MULTILINE | re.DOTALL)
+
def is_authed(self, force = False):
"""
Attempts to detect a current session
self._lastAuthed = time.time()
return True
- _tokenURL = "http://www.google.com/voice/m"
- _loginURL = "https://www.google.com/accounts/ServiceLoginAuth"
- _galxRe = re.compile(r"""<input.*?name="GALX".*?value="(.*?)".*?/>""", re.MULTILINE | re.DOTALL)
-
- def login(self, username, password):
- """
- Attempt to login to GoogleVoice
- @returns Whether login was successful or not
- """
+ def _get_token(self):
try:
tokenPage = self._browser.download(self._tokenURL)
except urllib2.URLError, e:
else:
galxToken = ""
_moduleLogger.debug("Could not grab GALX token")
+ return galxToken
+ def _login(self, username, password, token):
loginPostData = urllib.urlencode({
'Email' : username,
'Passwd' : password,
"ltmpl": "mobile",
"btmpl": "mobile",
"PersistentCookie": "yes",
- "GALX": galxToken,
+ "GALX": token,
"continue": self._forwardURL,
})
except urllib2.URLError, e:
_moduleLogger.exception("Translating error: %s" % str(e))
raise NetworkError("%s is not accesible" % self._loginURL)
+ return loginSuccessOrFailurePage
+
+ def login(self, username, password):
+ """
+ Attempt to login to GoogleVoice
+ @returns Whether login was successful or not
+ """
+ self.logout()
+ galxToken = self._get_token()
+ loginSuccessOrFailurePage = self._login(username, password, galxToken)
try:
self._grab_account_info(loginSuccessOrFailurePage)
except Exception, e:
- _moduleLogger.exception(str(e))
- return False
+ # Retry in case the redirect failed
+ # luckily is_authed does everything we need for a retry
+ loggedIn = self.is_authed(True)
+ if not loggedIn:
+ _moduleLogger.exception(str(e))
+ return False
+ _moduleLogger.info("Redirection failed on initial login attempt, auto-corrected for this")
self._browser.cookies.save()
self._lastAuthed = time.time()
self._browser.cookies.clear()
self._browser.cookies.save()
- _gvDialingStrRe = re.compile("This may take a few seconds", re.M)
- _clicktocallURL = "https://www.google.com/voice/m/sendcall"
-
def dial(self, number):
"""
This is the main function responsible for initating the callback
return True
- _sendSmsURL = "https://www.google.com/voice/m/sendsms"
-
def send_sms(self, number, message):
number = self._send_validation(number)
try:
return True
- _validateRe = re.compile("^[0-9]{10,}$")
-
def is_valid_syntax(self, number):
"""
@returns If This number be called ( syntax validation only )
return {}
return self._callbackNumbers
- _setforwardURL = "https://www.google.com//voice/m/setphone"
-
def set_callback_number(self, callbacknumber):
"""
Set the number that GoogleVoice calls
"""
return self._callbackNumber
- _recentCallsURL = "https://www.google.com/voice/inbox/recent/"
- _placedCallsURL = "https://www.google.com/voice/inbox/recent/placed/"
- _receivedCallsURL = "https://www.google.com/voice/inbox/recent/received/"
- _missedCallsURL = "https://www.google.com/voice/inbox/recent/missed/"
-
def get_recent(self):
"""
@returns Iterable of (personsName, phoneNumber, exact date, relative date, action)
recentCallData["action"] = action
yield recentCallData
- _contactsRe = re.compile(r"""<a href="/voice/m/contact/(\d+)">(.*?)</a>""", re.S)
- _contactsNextRe = re.compile(r""".*<a href="/voice/m/contacts(\?p=\d+)">Next.*?</a>""", re.S)
- _contactsURL = "https://www.google.com/voice/mobile/contacts"
-
def get_contacts(self):
"""
@returns Iterable of (contact id, contact name)
newContactsPageUrl = self._contactsURL + next_match.group(1)
contactsPagesUrls.append(newContactsPageUrl)
- _contactDetailPhoneRe = re.compile(r"""<div.*?>([0-9+\-\(\) \t]+?)<span.*?>\((\w+)\)</span>""", re.S)
- _contactDetailURL = "https://www.google.com/voice/mobile/contact"
-
def get_contact_details(self, contactId):
"""
@returns Iterable of (Phone Type, Phone Number)
phoneType = saxutils.unescape(detail_match.group(2))
yield (phoneType, phoneNumber)
- _voicemailURL = "https://www.google.com/voice/inbox/recent/voicemail/"
- _smsURL = "https://www.google.com/voice/inbox/recent/sms/"
-
def get_messages(self):
try:
voicemailPage = self._browser.download(self._voicemailURL)
_moduleLogger.exception("Translating error: %s" % str(e))
raise NetworkError("%s is not accesible" % self._voicemailURL)
voicemailHtml = self._grab_html(voicemailPage)
+ voicemailJson = self._grab_json(voicemailPage)
parsedVoicemail = self._parse_voicemail(voicemailHtml)
- decoratedVoicemails = self._decorate_voicemail(parsedVoicemail)
+ voicemails = self._merge_messages(parsedVoicemail, voicemailJson)
+ decoratedVoicemails = self._decorate_voicemail(voicemails)
try:
smsPage = self._browser.download(self._smsURL)
_moduleLogger.exception("Translating error: %s" % str(e))
raise NetworkError("%s is not accesible" % self._smsURL)
smsHtml = self._grab_html(smsPage)
+ smsJson = self._grab_json(smsPage)
parsedSms = self._parse_sms(smsHtml)
- decoratedSms = self._decorate_sms(parsedSms)
+ smss = self._merge_messages(parsedSms, smsJson)
+ decoratedSms = self._decorate_sms(smss)
allMessages = itertools.chain(decoratedVoicemails, decoratedSms)
return allMessages
flatHtml = htmlElement.text
return flatHtml
- _tokenRe = re.compile(r"""<input.*?name="_rnr_se".*?value="(.*?)"\s*/>""")
- _accountNumRe = re.compile(r"""<b class="ms\d">(.{14})</b></div>""")
- _callbackRe = re.compile(r"""\s+(.*?):\s*(.*?)<br\s*/>\s*$""", re.M)
- _forwardURL = "https://www.google.com/voice/mobile/phones"
-
def _grab_account_info(self, page):
tokenGroup = self._tokenRe.search(page)
if tokenGroup is None:
number = number[1:]
return number
- _seperateVoicemailsRegex = re.compile(r"""^\s*<div id="(\w+)"\s* class=".*?gc-message.*?">""", re.MULTILINE | re.DOTALL)
- _exactVoicemailTimeRegex = re.compile(r"""<span class="gc-message-time">(.*?)</span>""", re.MULTILINE)
- _relativeVoicemailTimeRegex = re.compile(r"""<span class="gc-message-relative">(.*?)</span>""", re.MULTILINE)
- _voicemailNameRegex = re.compile(r"""<a class=.*?gc-message-name-link.*?>(.*?)</a>""", re.MULTILINE | re.DOTALL)
- _voicemailNumberRegex = re.compile(r"""<input type="hidden" class="gc-text gc-quickcall-ac" value="(.*?)"/>""", re.MULTILINE)
- _prettyVoicemailNumberRegex = re.compile(r"""<span class="gc-message-type">(.*?)</span>""", re.MULTILINE)
- _voicemailLocationRegex = re.compile(r"""<span class="gc-message-location">.*?<a.*?>(.*?)</a></span>""", re.MULTILINE)
- _messagesContactID = re.compile(r"""<a class=".*?gc-message-name-link.*?">.*?</a>\s*?<span .*?>(.*?)</span>""", re.MULTILINE)
- #_voicemailMessageRegex = re.compile(r"""<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>""", re.MULTILINE)
- #_voicemailMessageRegex = re.compile(r"""<a .*? class="gc-message-mni">(.*?)</a>""", re.MULTILINE)
- _voicemailMessageRegex = re.compile(r"""(<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>|<a .*? class="gc-message-mni">(.*?)</a>)""", re.MULTILINE)
-
@staticmethod
def _interpret_voicemail_regex(group):
quality, content, number = group.group(2), group.group(3), group.group(4)
"number": number,
"location": location,
"messageParts": messageParts,
+ "type": "Voicemail",
}
def _decorate_voicemail(self, parsedVoicemails):
voicemailData["messageParts"] = ((whoFrom, message, when), )
yield voicemailData
- _smsFromRegex = re.compile(r"""<span class="gc-message-sms-from">(.*?)</span>""", re.MULTILINE | re.DOTALL)
- _smsTimeRegex = re.compile(r"""<span class="gc-message-sms-time">(.*?)</span>""", re.MULTILINE | re.DOTALL)
- _smsTextRegex = re.compile(r"""<span class="gc-message-sms-text">(.*?)</span>""", re.MULTILINE | re.DOTALL)
-
def _parse_sms(self, smsHtml):
splitSms = self._seperateVoicemailsRegex.split(smsHtml)
for messageId, messageHtml in itergroup(splitSms[1:], 2):
"number": number,
"location": "",
"messageParts": messageParts,
+ "type": "Texts",
}
def _decorate_sms(self, parsedTexts):
return parsedTexts
+ @staticmethod
+ def _merge_messages(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"]
+ yield message
+
def set_sane_callback(backend):
"""
return backend
+def grab_debug_info(username, password):
+ cookieFile = os.path.join(".", "raw_cookies.txt")
+ try:
+ os.remove(cookieFile)
+ except OSError:
+ pass
+
+ backend = GVoiceBackend(cookieFile)
+ browser = backend._browser
+
+ _TEST_WEBPAGES = [
+ ("forward", backend._forwardURL),
+ ("token", backend._tokenURL),
+ ("login", backend._loginURL),
+ ("contacts", backend._contactsURL),
+
+ ("voicemail", backend._voicemailURL),
+ ("sms", backend._smsURL),
+
+ ("recent", backend._recentCallsURL),
+ ("placed", backend._placedCallsURL),
+ ("recieved", backend._receivedCallsURL),
+ ("missed", backend._missedCallsURL),
+ ]
+
+ # Get Pages
+ print "Grabbing pre-login pages"
+ for name, url in _TEST_WEBPAGES:
+ try:
+ page = browser.download(url)
+ except StandardError, e:
+ print e.message
+ continue
+ print "\tWriting to file"
+ with open("not_loggedin_%s.txt" % name, "w") as f:
+ f.write(page)
+
+ # Login
+ print "Attempting login"
+ galxToken = backend._get_token()
+ loginSuccessOrFailurePage = backend._login(username, password, galxToken)
+ with open("loggingin.txt", "w") as f:
+ print "\tWriting to file"
+ f.write(loginSuccessOrFailurePage)
+ try:
+ backend._grab_account_info(loginSuccessOrFailurePage)
+ except Exception:
+ # Retry in case the redirect failed
+ # luckily is_authed does everything we need for a retry
+ loggedIn = backend.is_authed(True)
+ if not loggedIn:
+ raise
+
+ # Get Pages
+ print "Grabbing post-login pages"
+ for name, url in _TEST_WEBPAGES:
+ try:
+ page = browser.download(url)
+ except StandardError, e:
+ print e.message
+ continue
+ print "\tWriting to file"
+ with open("loggedin_%s.txt" % name, "w") as f:
+ f.write(page)
+
+ # Cookies
+ browser.cookies.save()
+ print "\tWriting cookies to file"
+ with open("cookies.txt", "w") as f:
+ f.writelines(
+ "%s: %s\n" % (c.name, c.value)
+ for c in browser.cookies
+ )
+
+
if __name__ == "__main__":
import sys
logging.basicConfig(level=logging.DEBUG)
- test_backend(sys.argv[1], sys.argv[2])
+ #test_backend(sys.argv[1], sys.argv[2])
+ grab_debug_info(sys.argv[1], sys.argv[2])