#!/usr/bin/python
"""
-DialCentral - Front end for Google's Grand Central service.
+DialCentral - Front end for Google's GoogleVoice service.
Copyright (C) 2008 Eric Warnke ericew AT gmail DOT com
This library is free software; you can redistribute it and/or
import time
import datetime
import itertools
-import warnings
-import traceback
+import logging
from xml.sax import saxutils
from xml.etree import ElementTree
simplejson = None
+_moduleLogger = logging.getLogger("gv_backend")
_TRUE_REGEX = re.compile("true")
_FALSE_REGEX = re.compile("false")
return itertools.izip(*nIterators)
-def abbrev_relative_date(date):
- """
- >>> abbrev_relative_date("42 hours ago")
- '42 h'
- >>> abbrev_relative_date("2 days ago")
- '2 d'
- >>> abbrev_relative_date("4 weeks ago")
- '4 w'
- """
- parts = date.split(" ")
- return "%s %s" % (parts[0], parts[1][0])
-
-
class GVDialer(object):
"""
- This class encapsulates all of the knowledge necessary to interace with the grandcentral servers
+ This class encapsulates all of the knowledge necessary to interact with the GoogleVoice servers
the functions include login, setting up a callback number, and initalting a callback
"""
try:
self._grab_account_info()
- except StandardError, e:
- warnings.warn(traceback.format_exc())
+ except Exception, e:
+ _moduleLogger.exception(str(e))
return False
self._browser.cookies.save()
def login(self, username, password):
"""
- Attempt to login to grandcentral
+ Attempt to login to GoogleVoice
@returns Whether login was successful or not
"""
if self.is_authed():
try:
loginSuccessOrFailurePage = self._browser.download(self._loginURL, loginPostData)
except urllib2.URLError, e:
- warnings.warn(traceback.format_exc())
+ _moduleLogger.exception(str(e))
raise RuntimeError("%s is not accesible" % self._loginURL)
return self.is_authed()
}
callSuccessPage = self._browser.download(self._clicktocallURL, clickToCallData, None, otherData)
except urllib2.URLError, e:
- warnings.warn(traceback.format_exc())
+ _moduleLogger.exception(str(e))
raise RuntimeError("%s is not accesible" % self._clicktocallURL)
if self._gvDialingStrRe.search(callSuccessPage) is None:
}
smsSuccessPage = self._browser.download(self._sendSmsURL, smsData, None, otherData)
except urllib2.URLError, e:
- warnings.warn(traceback.format_exc())
+ _moduleLogger.exception(str(e))
raise RuntimeError("%s is not accesible" % self._sendSmsURL)
return True
def get_account_number(self):
"""
- @returns The grand central phone number
+ @returns The GoogleVoice phone number
"""
return self._accountNum
def set_callback_number(self, callbacknumber):
"""
- Set the number that grandcental calls
+ Set the number that GoogleVoice calls
@param callbacknumber should be a proper 10 digit number
"""
self._callbackNumber = callbacknumber
-
- # Currently this isn't working out in GoogleVoice, but thats ok, we pass the callback on dial
- #callbackPostData = urllib.urlencode({
- # '_rnr_se': self._token,
- # 'phone': callbacknumber
- #})
- #try:
- # callbackSetPage = self._browser.download(self._setforwardURL, callbackPostData)
- # self._browser.cookies.save()
- #except urllib2.URLError, e:
- # warnings.warn(traceback.format_exc())
- # raise RuntimeError("%s is not accesible" % self._setforwardURL)
-
return True
def get_callback_number(self):
"""
@returns Current callback number or None
"""
- #for c in self._browser.cookies:
- # if c.name == "gv-ph":
- # return c.value
return self._callbackNumber
def get_recent(self):
]
sortedRecent.sort(reverse = True)
for exactDate, name, number, relativeDate, action in sortedRecent:
- relativeDate = abbrev_relative_date(relativeDate)
yield name, number, relativeDate, action
def get_addressbooks(self):
try:
contactsPage = self._browser.download(contactsPageUrl)
except urllib2.URLError, e:
- warnings.warn(traceback.format_exc())
+ _moduleLogger.exception(str(e))
raise RuntimeError("%s is not accesible" % contactsPageUrl)
for contact_match in self._contactsRe.finditer(contactsPage):
contactId = contact_match.group(1)
try:
detailPage = self._browser.download(self._contactDetailURL + '/' + contactId)
except urllib2.URLError, e:
- warnings.warn(traceback.format_exc())
+ _moduleLogger.exception(str(e))
raise RuntimeError("%s is not accesible" % self._contactDetailURL)
for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
try:
voicemailPage = self._browser.download(self._voicemailURL)
except urllib2.URLError, e:
- warnings.warn(traceback.format_exc())
+ _moduleLogger.exception(str(e))
raise RuntimeError("%s is not accesible" % self._voicemailURL)
voicemailHtml = self._grab_html(voicemailPage)
parsedVoicemail = self._parse_voicemail(voicemailHtml)
try:
smsPage = self._browser.download(self._smsURL)
except urllib2.URLError, e:
- warnings.warn(traceback.format_exc())
+ _moduleLogger.exception(str(e))
raise RuntimeError("%s is not accesible" % self._smsURL)
smsHtml = self._grab_html(smsPage)
parsedSms = self._parse_sms(smsHtml)
sortedMessages = list(allMessages)
sortedMessages.sort(reverse=True)
for exactDate, header, number, relativeDate, message in sortedMessages:
- relativeDate = abbrev_relative_date(relativeDate)
yield header, number, relativeDate, message
def _grab_json(self, flatXml):
if anGroup is not None:
self._accountNum = anGroup.group(1)
else:
- warnings.warn("Could not extract account number from GoogleVoice", UserWarning, 2)
+ _moduleLogger.debug("Could not extract account number from GoogleVoice")
self._callbackNumbers = {}
for match in self._callbackRe.finditer(page):
@returns Iterable of (personsName, phoneNumber, exact date, relative date, action)
"""
for action, url in (
- ("Recieved", self._receivedCallsURL),
+ ("Received", self._receivedCallsURL),
("Missed", self._missedCallsURL),
("Placed", self._placedCallsURL),
):
try:
flatXml = self._browser.download(url)
except urllib2.URLError, e:
- warnings.warn(traceback.format_exc())
+ _moduleLogger.exception(str(e))
raise RuntimeError("%s is not accesible" % url)
allRecentHtml = self._grab_html(flatXml)
header = "Unknown"
yield header, recentCallData["number"], exactTime, recentCallData["relTime"], action
- _seperateVoicemailsRegex = re.compile(r"""^\s*<div id="(\w+)"\s* class="gc-message.*?">""", re.MULTILINE | re.DOTALL)
+ _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)
- _voicemailMessageRegex = re.compile(r"""<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</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)
+ if quality is not None and content is not None:
+ return quality, content
+ elif number is not None:
+ return "high", number
def _parse_voicemail(self, voicemailHtml):
splitVoicemail = self._seperateVoicemailsRegex.split(voicemailHtml)
messageGroups = self._voicemailMessageRegex.finditer(messageHtml)
messageParts = (
- (group.group(1).strip(), group.group(2).strip())
+ self._interpret_voicemail_regex(group)
for group in messageGroups
) if messageGroups else ()
)).strip()
if not message:
message = "No Transcription"
- yield exactTime, header, voicemailData["number"], voicemailData["relTime"], message
+ yield exactTime, header, voicemailData["number"], voicemailData["relTime"], (message, )
_smsFromRegex = re.compile(r"""<span class="gc-message-sms-from">(.*?)</span>""", re.MULTILINE | re.DOTALL)
_smsTextRegex = re.compile(r"""<span class="gc-message-sms-time">(.*?)</span>""", re.MULTILINE | re.DOTALL)
header = "Unknown"
number = messageData["number"]
relativeTime = messageData["relTime"]
- message = "\n".join((
+ messages = [
"<b>%s</b>: %s" % (messagePart[0], messagePart[-1])
for messagePart in messageData["messageParts"]
- ))
- if not message:
- message = "No Transcription"
- yield exactTime, header, number, relativeTime, message
+ ]
+ if not messages:
+ messages = ("No Transcription", )
+ yield exactTime, header, number, relativeTime, messages
def test_backend(username, password):
- import pprint
backend = GVDialer()
print "Authenticated: ", backend.is_authed()
print "Login?: ", backend.login(username, password)
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: ",
# pprint.pprint(list(backend.get_recent()))
# for contact in backend.get_contacts():
# print contact
# pprint.pprint(list(backend.get_contact_details(contact[0])))
+ for message in backend.get_messages():
+ pprint.pprint(message)
return backend