X-Git-Url: http://git.maemo.org/git/?p=gc-dialer;a=blobdiff_plain;f=src%2Fgc_backend.py;h=ec02e9002d70e3b568486835fb95cebc14107347;hp=50f2ece7ce5b5ba38bfa20c52a692413200b5fa1;hb=1a4e7c927e6772ec8fb6a4142d91c1a7fef99136;hpb=c68aa200522bfbb312150e4e90cbdab1d2b1197a diff --git a/src/gc_backend.py b/src/gc_backend.py index 50f2ece..ec02e90 100644 --- a/src/gc_backend.py +++ b/src/gc_backend.py @@ -1,23 +1,23 @@ #!/usr/bin/python -# DialCentral - Front end for Google's Grand Central service. -# Copyright (C) 2008 Eric Warnke ericew AT gmail DOT com -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - """ +DialCentral - Front end for Google's Grand Central service. +Copyright (C) 2008 Eric Warnke ericew AT gmail DOT com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Grandcentral backend code """ @@ -28,13 +28,10 @@ import urllib import urllib2 import time import warnings +import traceback +from xml.sax import saxutils -from browser_emu import MozillaEmulator - -import socket - - -socket.setdefaulttimeout(5) +import browser_emu class GCDialer(object): @@ -47,11 +44,10 @@ class GCDialer(object): _accessTokenRe = re.compile(r"""]*value="(.*)"/>""") _isLoginPageRe = re.compile(r"""
""") _callbackRe = re.compile(r"""name="default_number" value="(\d+)" />\s+(.*)\s$""", re.M) - _accountNumRe = re.compile(r"""GrandCentral\s*(.{14})\s* """, re.M) + _accountNumRe = re.compile(r"""]*value="(.*)"/>""") _inboxRe = re.compile(r""".*?(voicemail|received|missed|call return).*?\s+\s+\s+(.*?)\s+ \| \s+(.*?)\s?\s+
\s+(.*?)\s?(.*?)""", re.S) _contactsNextRe = re.compile(r""".*Next""", re.S) - _contactDetailGroupRe = re.compile(r"""Group:\s*(\w*)""", re.S) _contactDetailPhoneRe = re.compile(r"""(\w+):[0-9\-\(\) \t]*?call""", re.S) _validateRe = re.compile("^[0-9]{10,}$") @@ -66,7 +62,7 @@ class GCDialer(object): def __init__(self, cookieFile = None): # Important items in this function are the setup of the browser emulation and cookie file - self._browser = MozillaEmulator(None, 0) + self._browser = browser_emu.MozillaEmulator(1) if cookieFile is None: cookieFile = os.path.join(os.path.expanduser("~"), ".gc_cookies.txt") self._browser.cookies.filename = cookieFile @@ -74,9 +70,9 @@ class GCDialer(object): self._browser.cookies.load() self._accessToken = None - self._accountNum = None - self._callbackNumbers = {} + self._accountNum = "" self._lastAuthed = 0.0 + self._callbackNumbers = {} self.__contacts = None @@ -91,18 +87,23 @@ class GCDialer(object): return True try: - forwardSelectionPage = self._browser.download(GCDialer._forwardselectURL) + forwardSelectionPage = self._browser.download(self._forwardselectURL) except urllib2.URLError, e: - warnings.warn("%s is not accesible" % GCDialer._forwardselectURL, UserWarning, 2) + warnings.warn(traceback.format_exc()) return False - self._browser.cookies.save() - if GCDialer._isLoginPageRe.search(forwardSelectionPage) is None: + if self._isLoginPageRe.search(forwardSelectionPage) is not None: + return False + + try: self._grab_token(forwardSelectionPage) - self._lastAuthed = time.time() - return True + except StandardError, e: + warnings.warn(traceback.format_exc()) + return False - return False + self._browser.cookies.save() + self._lastAuthed = time.time() + return True def login(self, username, password): """ @@ -115,10 +116,10 @@ class GCDialer(object): loginPostData = urllib.urlencode( {'username' : username , 'password' : password } ) try: - loginSuccessOrFailurePage = self._browser.download(GCDialer._loginURL, loginPostData) + loginSuccessOrFailurePage = self._browser.download(self._loginURL, loginPostData) except urllib2.URLError, e: - warnings.warn("%s is not accesible" % GCDialer._loginURL, UserWarning, 2) - return False + warnings.warn(traceback.format_exc()) + raise RuntimeError("%s is not accesible" % self._loginURL) return self.is_authed() @@ -133,33 +134,33 @@ class GCDialer(object): """ This is the main function responsible for initating the callback """ - # If the number is not valid throw exception if not self.is_valid_syntax(number): - raise ValueError('number is not valid') + raise ValueError('Number is not valid: "%s"' % number) + elif not self.is_authed(): + raise RuntimeError("Not Authenticated") - # No point if we don't have the magic cookie - if not self.is_authed(): - raise RunetimeError("Not Authenticated") - - # Strip leading 1 from 11 digit dialing if len(number) == 11 and number[0] == 1: + # Strip leading 1 from 11 digit dialing number = number[1:] try: callSuccessPage = self._browser.download( - GCDialer._clicktocallURL % (self._accessToken, number), + self._clicktocallURL % (self._accessToken, number), None, {'Referer' : 'http://www.grandcentral.com/mobile/messages'} ) except urllib2.URLError, e: - warnings.warn("%s is not accesible" % GCDialer._clicktocallURL, UserWarning, 2) - raise RunetimeError("%s is not accesible" % GCDialer._clicktocallURL) + warnings.warn(traceback.format_exc()) + raise RuntimeError("%s is not accesible" % self._clicktocallURL) - if GCDialer._gcDialingStrRe.search(callSuccessPage) is None: + if self._gcDialingStrRe.search(callSuccessPage) is None: raise RuntimeError("Grand Central returned an error") return True + def send_sms(self, number, message): + raise NotImplementedError("SMS Is Not Supported by GrandCentral") + def clear_caches(self): self.__contacts = None @@ -186,17 +187,17 @@ class GCDialer(object): numbers = self.get_callback_numbers() for number, description in numbers.iteritems(): - if not re.compile(r"""1747""").match(number) is None: + if re.compile(r"""1747""").match(number) is not None: self.set_callback_number(number) return for number, description in numbers.iteritems(): - if not re.compile(r"""gizmo""", re.I).search(description) is None: + if re.compile(r"""gizmo""", re.I).search(description) is not None: self.set_callback_number(number) return for number, description in numbers.iteritems(): - if not re.compile(r"""computer""", re.I).search(description) is None: + if re.compile(r"""computer""", re.I).search(description) is not None: self.set_callback_number(number) return @@ -224,10 +225,10 @@ class GCDialer(object): 'default_number': callbacknumber }) try: - callbackSetPage = self._browser.download(GCDialer._setforwardURL, callbackPostData) + callbackSetPage = self._browser.download(self._setforwardURL, callbackPostData) except urllib2.URLError, e: - warnings.warn("%s is not accesible" % GCDialer._setforwardURL, UserWarning, 2) - return False + warnings.warn(traceback.format_exc()) + raise RuntimeError("%s is not accesible" % self._setforwardURL) self._browser.cookies.save() return True @@ -239,23 +240,23 @@ class GCDialer(object): for c in self._browser.cookies: if c.name == "pda_forwarding_number": return c.value - return None + return "" def get_recent(self): """ @returns Iterable of (personsName, phoneNumber, date, action) """ try: - recentCallsPage = self._browser.download(GCDialer._inboxallURL) + recentCallsPage = self._browser.download(self._inboxallURL) except urllib2.URLError, e: - warnings.warn("%s is not accesible" % GCDialer._inboxallURL, UserWarning, 2) - return + warnings.warn(traceback.format_exc()) + raise RuntimeError("%s is not accesible" % self._inboxallURL) for match in self._inboxRe.finditer(recentCallsPage): phoneNumber = match.group(4) - action = match.group(1) - date = match.group(2) - personsName = match.group(3) + action = saxutils.unescape(match.group(1)) + date = saxutils.unescape(match.group(2)) + personsName = saxutils.unescape(match.group(3)) yield personsName, phoneNumber, date, action def get_addressbooks(self): @@ -282,13 +283,17 @@ class GCDialer(object): if self.__contacts is None: self.__contacts = [] - contactsPagesUrls = [GCDialer._contactsURL] + contactsPagesUrls = [self._contactsURL] for contactsPageUrl in contactsPagesUrls: - contactsPage = self._browser.download(contactsPageUrl) + try: + contactsPage = self._browser.download(contactsPageUrl) + except urllib2.URLError, e: + warnings.warn(traceback.format_exc()) + raise RuntimeError("%s is not accesible" % contactsPageUrl) for contact_match in self._contactsRe.finditer(contactsPage): contactId = contact_match.group(1) contactName = contact_match.group(2) - contact = contactId, contactName + contact = contactId, saxutils.unescape(contactName) self.__contacts.append(contact) yield contact @@ -304,20 +309,54 @@ class GCDialer(object): """ @returns Iterable of (Phone Type, Phone Number) """ - detailPage = self._browser.download(GCDialer._contactDetailURL + '/' + contactId) + try: + detailPage = self._browser.download(self._contactDetailURL + '/' + contactId) + except urllib2.URLError, e: + warnings.warn(traceback.format_exc()) + raise RuntimeError("%s is not accesible" % self._contactDetailURL) + for detail_match in self._contactDetailPhoneRe.finditer(detailPage): - phoneType = detail_match.group(1) + phoneType = saxutils.unescape(detail_match.group(1)) phoneNumber = detail_match.group(2) yield (phoneType, phoneNumber) + def get_messages(self): + return () + def _grab_token(self, data): "Pull the magic cookie from the datastream" - atGroup = GCDialer._accessTokenRe.search(data) + atGroup = self._accessTokenRe.search(data) + if atGroup is None: + raise RuntimeError("Could not extract authentication token from GrandCentral") self._accessToken = atGroup.group(1) - anGroup = GCDialer._accountNumRe.search(data) - self._accountNum = anGroup.group(1) + anGroup = self._accountNumRe.search(data) + if anGroup is not None: + self._accountNum = anGroup.group(1) + else: + warnings.warn("Could not extract account number from GrandCentral", UserWarning, 2) self._callbackNumbers = {} - for match in GCDialer._callbackRe.finditer(data): + for match in self._callbackRe.finditer(data): self._callbackNumbers[match.group(1)] = match.group(2) + + +def test_backend(username, password): + import pprint + backend = GCDialer() + print "Authenticated: ", backend.is_authed() + print "Login?: ", backend.login(username, password) + print "Authenticated: ", backend.is_authed() + # print "Token: ", backend._accessToken + print "Account: ", backend.get_account_number() + print "Callback: ", backend.get_callback_number() + # print "All Callback: ", + # pprint.pprint(backend.get_callback_numbers()) + # print "Recent: ", + # pprint.pprint(list(backend.get_recent())) + # print "Contacts: ", + # for contact in backend.get_contacts(): + # print contact + # pprint.pprint(list(backend.get_contact_details(contact[0]))) + + return backend