From 426e8a115afe19b3c3434cab91624ef90bfb28cc Mon Sep 17 00:00:00 2001 From: epage Date: Fri, 13 Mar 2009 02:34:53 +0000 Subject: [PATCH] Work in progress on Google Voice and Google Data git-svn-id: file:///svnroot/gc-dialer/trunk@215 c39d3808-3fe2-4d86-a59f-b7f623ee9f21 --- Makefile | 1 + src/gdata_backend.py | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/gmail_backend.py | 6 +-- src/gv_backend.py | 72 ++++++++++++++++------------ 4 files changed, 172 insertions(+), 34 deletions(-) create mode 100644 src/gdata_backend.py diff --git a/Makefile b/Makefile index 9069a40..56869db 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ SOURCE=$(SOURCE_PATH)/dialer.py \ $(SOURCE_PATH)/null_views.py \ $(SOURCE_PATH)/file_backend.py \ $(SOURCE_PATH)/evo_backend.py \ + $(SOURCE_PATH)/gdata_backend.py \ $(SOURCE_PATH)/gv_backend.py \ $(SOURCE_PATH)/gc_backend.py \ $(SOURCE_PATH)/browser_emu.py \ diff --git a/src/gdata_backend.py b/src/gdata_backend.py new file mode 100644 index 0000000..a1b3ca4 --- /dev/null +++ b/src/gdata_backend.py @@ -0,0 +1,127 @@ +#!/usr/bin/python + +import sys +sys.path.insert(0,"../gdata/src/") + + +try: + import atom + import gdata + gdata.contacts +except (ImportError, AttributeError): + atom = None + gdata = None + + +class GDataAddressBook(object): + """ + """ + + def __init__(self, client, id = None): + self._client = client + self._id = id + self._contacts = [] + + def clear_caches(self): + del self._contacts[:] + + @staticmethod + def factory_name(): + return "GData" + + @staticmethod + def contact_source_short_name(contactId): + return "gd" + + def get_contacts(self): + """ + @returns Iterable of (contact id, contact name) + """ + return [] + + def get_contact_details(self, contactId): + """ + @returns Iterable of (Phone Type, Phone Number) + """ + return [] + + def _get_contacts(self): + if len(self._contacts) != 0: + return self._contacts + feed = self._get_feed() + for entry in feed: + name = entry.title.text + print name + for extendedProperty in entry.extended_property: + if extendedProperty.value: + print extendedProperty.value + else: + print extendedProperty.GetXmlBlobString() + + def _get_feed(self): + if self._id is None: + return self._client.GetContactsFeed() + else: + pass + + +class GDataAddressBookFactory(object): + + def __init__(self, username, password): + self._username = username + self._password = password + self._client = None + + if gdata is None: + return + self._client = gdata.contacts.service.ContactsService() + self._client.email = username + self._client.password = password + self._client.source = "DialCentra" + self._client.ProgrammaticLogin() + + def clear_caches(self): + if gdata is None: + return + pass + + def get_addressbooks(self): + """ + @returns Iterable of (Address Book Factory, Book Id, Book Name) + """ + if gdata is None: + return + feed = self._client.GetGroupsFeed() + for entry in feed: + id = entry.id.text + name = entry.title.text + yield self, id, name + yield self, "all", "All" + + def open_addressbook(self, bookId): + if gdata is None: + return + if bookId == "all": + return GDataAddressBook(self._client) + else: + return GDataAddressBook(self._client, bookId) + + @staticmethod + def factory_name(): + return "GData" + + +def print_gbooks(username, password): + """ + Included here for debugging. + + Either insert it into the code or launch python with the "-i" flag + """ + abf = GDataAddressBookFactory(username, password) + for book in abf.get_addressbooks(): + ab = abf.open_addressbook(book[1]) + print book + for contact in ab.get_contacts(): + print "\t", contact + for details in ab.get_contact_details(contact[0]): + print "\t\t", details diff --git a/src/gmail_backend.py b/src/gmail_backend.py index d80ae7f..bead963 100644 --- a/src/gmail_backend.py +++ b/src/gmail_backend.py @@ -45,7 +45,7 @@ class GMailAddressBook(object): self._account = libgmail.GmailAccount(username, password) self._gmailContacts = self._account.getContacts() - + @classmethod def is_supported(cls): return libgmail is not None @@ -58,7 +58,7 @@ class GMailAddressBook(object): return yield self, "", "" - + def open_addressbook(self, bookId): return self @@ -77,7 +77,7 @@ class GMailAddressBook(object): if not self.is_supported(): return pass - + def get_contact_details(self, contactId): """ @returns Iterable of (Phone Type, Phone Number) diff --git a/src/gv_backend.py b/src/gv_backend.py index 227838b..35163a8 100644 --- a/src/gv_backend.py +++ b/src/gv_backend.py @@ -37,13 +37,13 @@ import socket socket.setdefaulttimeout(5) -class GCDialer(object): +class GVDialer(object): """ This class encapsulates all of the knowledge necessary to interace with the grandcentral servers the functions include login, setting up a callback number, and initalting a callback """ - _gcDialingStrRe = re.compile("This may take a few seconds", re.M) + _gvDialingStrRe = re.compile("This may take a few seconds", re.M) _accessTokenRe = re.compile(r"""]*value="(.*)"/>""") _isLoginPageRe = re.compile(r"""
""") _callbackRe = re.compile(r"""name="default_number" value="(\d+)" />\s+(.*)\s$""", re.M) @@ -56,19 +56,19 @@ class GCDialer(object): _validateRe = re.compile("^[0-9]{10,}$") - _forwardselectURL = "http://www.grandcentral.com/mobile/settings/forwarding_select" - _loginURL = "https://www.grandcentral.com/mobile/account/login" - _setforwardURL = "http://www.grandcentral.com/mobile/settings/set_forwarding?from=settings" - _clicktocallURL = "http://www.grandcentral.com/mobile/calls/click_to_call?a_t=%s&destno=%s" - _inboxallURL = "http://www.grandcentral.com/mobile/messages/inbox?types=all" - _contactsURL = "http://www.grandcentral.com/mobile/contacts" - _contactDetailURL = "http://www.grandcentral.com/mobile/contacts/detail" + _forwardselectURL = "http://www.google.com/voice/m/settings/forwarding_select" + _loginURL = "https://www.google.com/voice/m/account/login" + _setforwardURL = "http://www.google.com/voice/m/settings/set_forwarding?from=settings" + _clicktocallURL = "http://www.google.com/voice/m/caller?number=%s" + _inboxallURL = "http://www.google.com/voice/m/i" + _contactsURL = "http://www.google.com/voice/m/contacts" + _contactDetailURL = "http://www.google.com/voice/m/contact" 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) if cookieFile is None: - cookieFile = os.path.join(os.path.expanduser("~"), ".gc_cookies.txt") + cookieFile = os.path.join(os.path.expanduser("~"), ".gv_cookies.txt") self._browser.cookies.filename = cookieFile if os.path.isfile(cookieFile): self._browser.cookies.load() @@ -91,12 +91,12 @@ class GCDialer(object): return True try: - forwardSelectionPage = self._browser.download(GCDialer._forwardselectURL) + forwardSelectionPage = self._browser.download(self._forwardselectURL) except urllib2.URLError, e: - raise RuntimeError("%s is not accesible" % GCDialer._clicktocallURL) + raise RuntimeError("%s is not accesible" % self._clicktocallURL) self._browser.cookies.save() - if GCDialer._isLoginPageRe.search(forwardSelectionPage) is None: + if self._isLoginPageRe.search(forwardSelectionPage) is None: self._grab_token(forwardSelectionPage) self._lastAuthed = time.time() return True @@ -114,9 +114,9 @@ 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: - raise RuntimeError("%s is not accesible" % GCDialer._clicktocallURL) + raise RuntimeError("%s is not accesible" % self._clicktocallURL) return self.is_authed() @@ -145,14 +145,14 @@ class GCDialer(object): try: callSuccessPage = self._browser.download( - GCDialer._clicktocallURL % (self._accessToken, number), + self._clicktocallURL % (number, ), None, {'Referer' : 'http://www.grandcentral.com/mobile/messages'} ) except urllib2.URLError, e: - raise RuntimeError("%s is not accesible" % GCDialer._clicktocallURL) + raise RuntimeError("%s is not accesible" % self._clicktocallURL) - if GCDialer._gcDialingStrRe.search(callSuccessPage) is None: + if self._gvDialingStrRe.search(callSuccessPage) is None: raise RuntimeError("Grand Central returned an error") return True @@ -221,9 +221,9 @@ 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: - raise RuntimeError("%s is not accesible" % GCDialer._clicktocallURL) + raise RuntimeError("%s is not accesible" % self._clicktocallURL) self._browser.cookies.save() return True @@ -242,9 +242,9 @@ class GCDialer(object): @returns Iterable of (personsName, phoneNumber, date, action) """ try: - recentCallsPage = self._browser.download(GCDialer._inboxallURL) + recentCallsPage = self._browser.download(self._inboxallURL) except urllib2.URLError, e: - raise RuntimeError("%s is not accesible" % GCDialer._clicktocallURL) + raise RuntimeError("%s is not accesible" % self._clicktocallURL) for match in self._inboxRe.finditer(recentCallsPage): phoneNumber = match.group(4) @@ -264,11 +264,11 @@ class GCDialer(object): @staticmethod def contact_source_short_name(contactId): - return "GC" + return "GV" @staticmethod def factory_name(): - return "Grand Central" + return "Google Voice" def get_contacts(self): """ @@ -277,12 +277,12 @@ class GCDialer(object): if self.__contacts is None: self.__contacts = [] - contactsPagesUrls = [GCDialer._contactsURL] + contactsPagesUrls = [self._contactsURL] for contactsPageUrl in contactsPagesUrls: try: contactsPage = self._browser.download(contactsPageUrl) except urllib2.URLError, e: - raise RuntimeError("%s is not accesible" % GCDialer._clicktocallURL) + raise RuntimeError("%s is not accesible" % self._clicktocallURL) for contact_match in self._contactsRe.finditer(contactsPage): contactId = contact_match.group(1) contactName = contact_match.group(2) @@ -303,9 +303,9 @@ class GCDialer(object): @returns Iterable of (Phone Type, Phone Number) """ try: - detailPage = self._browser.download(GCDialer._contactDetailURL + '/' + contactId) + detailPage = self._browser.download(self._contactDetailURL + '/' + contactId) except urllib2.URLError, e: - raise RuntimeError("%s is not accesible" % GCDialer._clicktocallURL) + raise RuntimeError("%s is not accesible" % self._clicktocallURL) for detail_match in self._contactDetailPhoneRe.finditer(detailPage): phoneType = detail_match.group(1) @@ -314,12 +314,22 @@ class GCDialer(object): def _grab_token(self, data): "Pull the magic cookie from the datastream" - atGroup = GCDialer._accessTokenRe.search(data) + atGroup = self._accessTokenRe.search(data) self._accessToken = atGroup.group(1) - anGroup = GCDialer._accountNumRe.search(data) + anGroup = self._accountNumRe.search(data) self._accountNum = anGroup.group(1) 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): + backend = GVDialer() + print "Authenticated: ", backend.is_authed() + backend.login(username, password) + print "Authenticated: ", backend.is_authed() + print "Account: ", backend.get_account_number() + print "Callback: ", backend.get_callback_numbers() + print "Recent: ", backend.get_recent() -- 1.7.9.5