Backend support for evolution contacts. Still not integrated into the GUI
authorepage <eopage@byu.net>
Fri, 8 Aug 2008 02:05:49 +0000 (02:05 +0000)
committerepage <eopage@byu.net>
Fri, 8 Aug 2008 02:05:49 +0000 (02:05 +0000)
git-svn-id: file:///svnroot/gc-dialer/trunk@124 c39d3808-3fe2-4d86-a59f-b7f623ee9f21

Makefile
src/evo_backend.py [new file with mode: 0644]
src/gc_backend.py [new file with mode: 0644]
src/gc_dialer.py
src/gcbackend.py [deleted file]

index bd97baa..e3758ab 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 PROJECT_NAME=gc_dialer
 PROJECT_VERSION=0.8.0
 SOURCE_PATH=src
-SOURCE=$(SOURCE_PATH)/gc_dialer.py $(SOURCE_PATH)/gcbackend.py $(SOURCE_PATH)/browser_emu.py
+SOURCE=$(SOURCE_PATH)/gc_dialer.py $(SOURCE_PATH)/evo_backend.py $(SOURCE_PATH)/gc_backend.py $(SOURCE_PATH)/browser_emu.py
 OBJ=$(SOURCE:.py=.pyc)
 LINT_STATS_PATH=~/.pylint.d
 LINT_STATS=$(foreach file, $(addsuffix 1.stats,$(subst /,.,$(basename $(SOURCE)))), $(LINT_STATS_PATH)/$(file) )
@@ -125,8 +125,8 @@ $(BUILD_BIN): $(SOURCE)
        #Construct the program by cat-ing all the python files together
        echo "#!/usr/bin/python" > $(BUILD_BIN)
        #echo "from __future__ import with_statement" >> $(PRE_PACKAGE_PATH)/usr/local/bin/gc_dialer.py
-       cat $(SOURCE_PATH)/gc_dialer.py $(SOURCE_PATH)/gcbackend.py $(SOURCE_PATH)/browser_emu.py | grep -e '^import ' | sort -u >> $(BUILD_BIN)
-       cat $(SOURCE_PATH)/browser_emu.py $(SOURCE_PATH)/gcbackend.py $(SOURCE_PATH)/gc_dialer.py | grep -v 'browser_emu' | grep -v 'gcbackend' | grep -v "#!" >> $(BUILD_BIN)
+       cat $(SOURCE_PATH)/gc_dialer.py $(SOURCE_PATH)/evo_backend.py $(SOURCE_PATH)/gc_backend.py $(SOURCE_PATH)/browser_emu.py | grep -e '^import ' | sort -u >> $(BUILD_BIN)
+       cat $(SOURCE_PATH)/browser_emu.py $(SOURCE_PATH)/evo_backend.py $(SOURCE_PATH)/gc_backend.py $(SOURCE_PATH)/gc_dialer.py | grep -v 'browser_emu' | grep -v 'gc_backend' | grep -v "evo_backend"| grep -v "#!" >> $(BUILD_BIN)
        chmod 755 $(BUILD_BIN)
 
 $(TAG_FILE): $(SOURCE)
diff --git a/src/evo_backend.py b/src/evo_backend.py
new file mode 100644 (file)
index 0000000..96d921a
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/python
+
+# GC Dialer - 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
+
+
+"""
+Evolution Contact Support
+"""
+
+
+try:
+       import evolution
+except ImportError:
+       evolution = None
+
+
+class EvolutionAddressBook(object):
+
+       def __init__(self, bookId = None):
+               if not self.is_supported():
+                       return
+
+               self._phoneTypes = None
+               self._bookId = bookId if bookId is not None else self.get_addressbooks().next()[1]
+               self._book = evolution.ebook.open_addressbook(self._bookId[1])
+       
+       @classmethod
+       def is_supported(cls):
+               return evolution is not None
+
+       def get_addressbooks(self):
+               """
+               @returns Iterable of (Address Book Factory, Book Id, Book Name)
+               """
+               if not self.is_supported():
+                       return
+
+               for bookId in evolution.ebook.list_addressbooks():
+                       yield self, bookId, bookId[0]
+       
+       def open_addressbook(self, bookId):
+               self._bookId = bookId
+               self._book = evolution.ebook.open_addressbook(self._bookId[1])
+               return self
+
+       def get_contacts(self):
+               """
+               @returns Iterable of (contact id, contact name)
+               """
+               if not self.is_supported():
+                       return
+
+               for contact in self._book.get_all_contacts():
+                       yield contact.get_uid(), contact.props.full_name
+       
+       def get_contact_details(self, contactId):
+               """
+               @returns Iterable of (Phone Type, Phone Number)
+               """
+               contact = self._book.get_contact(contactId)
+
+               if self._phoneTypes is None and contact is not None:
+                       self._phoneTypes = [pt for pt in dir(contact.props) if "phone" in pt.lower()]
+
+               for phoneType in self._phoneTypes:
+                       phoneNumber = getattr(contact.props, phoneType)
+                       if isinstance(phoneNumber, str):
+                               yield phoneType, phoneNumber
+
+def print_addressbooks():
+       """
+       Included here for debugging.
+
+       Either insert it into the code or launch python with the "-i" flag
+       """
+       if not EvolutionAddressBook.is_supported():
+               print "No Evolution Support"
+               return
+
+       eab = EvolutionAddressBook()
+       for book in eab.get_addressbooks():
+               eab = eab.open_addressbook(book[1])
+               print book
+               for contact in eab.get_contacts():
+                       print "\t", contact
+                       for details in eab.get_contact_details(contact[0]):
+                               print "\t\t", details
diff --git a/src/gc_backend.py b/src/gc_backend.py
new file mode 100644 (file)
index 0000000..f7d90b6
--- /dev/null
@@ -0,0 +1,307 @@
+#!/usr/bin/python
+
+# GC Dialer - 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 Dialer backend code
+"""
+
+
+import os
+import re
+import urllib
+import urllib2
+import time
+import warnings
+
+from browser_emu import MozillaEmulator
+
+
+class GCDialer(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)
+       _accessTokenRe = re.compile(r"""<input type="hidden" name="a_t" [^>]*value="(.*)"/>""")
+       _isLoginPageRe = re.compile(r"""<form method="post" action="https://www.grandcentral.com/mobile/account/login">""")
+       _callbackRe = re.compile(r"""name="default_number" value="(\d+)" />\s+(.*)\s$""", re.M)
+       _accountNumRe = re.compile(r"""<img src="/images/mobile/inbox_logo.gif" alt="GrandCentral" />\s*(.{14})\s*&nbsp""", re.M)
+       _inboxRe = re.compile(r"""<td>.*?(voicemail|received|missed|call return).*?</td>\s+<td>\s+<font size="2">\s+(.*?)\s+&nbsp;\|&nbsp;\s+<a href="/mobile/contacts/.*?">(.*?)\s?</a>\s+<br/>\s+(.*?)\s?<a href=""", re.S)
+       _contactsRe = re.compile(r"""<a href="/mobile/contacts/detail/(\d+)">(.*?)</a>""", re.S)
+       _contactsNextRe = re.compile(r""".*<a href="/mobile/contacts(\?page=\d+)">Next</a>""", re.S)
+       _contactDetailGroupRe   = re.compile(r"""Group:\s*(\w*)""", re.S)
+       _contactDetailPhoneRe   = re.compile(r"""(\w+):[0-9\-\(\) \t]*?<a href="/mobile/calls/click_to_call\?destno=(\d+).*?">call</a>""", re.S)
+
+       _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"
+
+       def __init__(self, cookieFile = None):
+               # Important items in this function are the setup of the browser emulation and cookie file
+               self._msg = ""
+
+               self._browser = MozillaEmulator(None, 0)
+               if cookieFile is None:
+                       cookieFile = os.path.join(os.path.expanduser("~"), ".gc_dialer_cookies.txt")
+               self._browser.cookies.filename = cookieFile
+               if os.path.isfile(cookieFile):
+                       self._browser.cookies.load()
+
+               self._accessToken = None
+               self._accountNum = None
+               self._callbackNumbers = {}
+               self._lastAuthed = 0.0
+
+       def is_authed(self, force = False):
+               """
+               Attempts to detect a current session and pull the auth token ( a_t ) from the page.
+               @note Once logged in try not to reauth more than once a minute.
+               @returns If authenticated
+               """
+
+               if time.time() - self._lastAuthed < 60 and not force:
+                       return True
+
+               try:
+                       forwardSelectionPage = self._browser.download(GCDialer._forwardselectURL)
+               except urllib2.URLError, e:
+                       warnings.warn("%s is not accesible" % GCDialer._forwardselectURL, UserWarning, 2)
+                       return False
+
+               self._browser.cookies.save()
+               if GCDialer._isLoginPageRe.search(forwardSelectionPage) is None:
+                       self._grab_token(forwardSelectionPage)
+                       self._lastAuthed = time.time()
+                       return True
+
+               return False
+
+       def login(self, username, password):
+               """
+               Attempt to login to grandcentral
+               @returns Whether login was successful or not
+               """
+               if self.is_authed():
+                       return True
+
+               loginPostData = urllib.urlencode( {'username' : username , 'password' : password } )
+
+               try:
+                       loginSuccessOrFailurePage = self._browser.download(GCDialer._loginURL, loginPostData)
+               except urllib2.URLError, e:
+                       warnings.warn("%s is not accesible" % GCDialer._loginURL, UserWarning, 2)
+                       return False
+
+               return self.is_authed()
+
+       def logout(self):
+               self._lastAuthed = 0.0
+               self._browser.cookies.clear()
+               self._browser.cookies.save()
+
+       def dial(self, number):
+               """
+               This is the main function responsible for initating the callback
+               """
+               self._msg = ""
+
+               # If the number is not valid throw exception
+               if not self.is_valid_syntax(number):
+                       raise ValueError('number is not valid')
+
+               # No point if we don't have the magic cookie
+               if not self.is_authed():
+                       self._msg = "Not authenticated"
+                       return False
+
+               # Strip leading 1 from 11 digit dialing
+               if len(number) == 11 and number[0] == 1:
+                       number = number[1:]
+
+               try:
+                       callSuccessPage = self._browser.download(
+                               GCDialer._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)
+                       return False
+
+               if GCDialer._gcDialingStrRe.search(callSuccessPage) is not None:
+                       return True
+               else:
+                       self._msg = "Grand Central returned an error"
+                       return False
+
+               self._msg = "Unknown Error"
+               return False
+
+       def clear_caches(self):
+               pass
+
+       def is_valid_syntax(self, number):
+               """
+               @returns If This number be called ( syntax validation only )
+               """
+               return self._validateRe.match(number) is not None
+
+       def get_account_number(self):
+               """
+               @returns The grand central phone number
+               """
+               return self._accountNum
+
+       def set_sane_callback(self):
+               """
+               Try to set a sane default callback number on these preferences
+               1) 1747 numbers ( Gizmo )
+               2) anything with gizmo in the name
+               3) anything with computer in the name
+               4) the first value
+               """
+               numbers = self.get_callback_numbers()
+
+               for number, description in numbers.iteritems():
+                       if not re.compile(r"""1747""").match(number) is 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:
+                               self.set_callback_number(number)
+                               return
+
+               for number, description in numbers.iteritems():
+                       if not re.compile(r"""computer""", re.I).search(description) is None:
+                               self.set_callback_number(number)
+                               return
+
+               for number, description in numbers.iteritems():
+                       self.set_callback_number(number)
+                       return
+
+       def get_callback_numbers(self):
+               """
+               @returns a dictionary mapping call back numbers to descriptions
+               @note These results are cached for 30 minutes.
+               """
+               if time.time() - self._lastAuthed < 1800 or self.is_authed():
+                       return self._callbackNumbers
+
+               return {}
+
+       def set_callback_number(self, callbacknumber):
+               """
+               Set the number that grandcental calls
+               @param callbacknumber should be a proper 10 digit number
+               """
+               callbackPostData = urllib.urlencode({
+                       'a_t': self._accessToken,
+                       'default_number': callbacknumber
+               })
+               try:
+                       callbackSetPage = self._browser.download(GCDialer._setforwardURL, callbackPostData)
+               except urllib2.URLError, e:
+                       warnings.warn("%s is not accesible" % GCDialer._setforwardURL, UserWarning, 2)
+                       return False
+
+               self._browser.cookies.save()
+               return True
+
+       def get_callback_number(self):
+               """
+               @returns Current callback number or None
+               """
+               for c in self._browser.cookies:
+                       if c.name == "pda_forwarding_number":
+                               return c.value
+               return None
+
+       def get_recent(self):
+               """
+               @returns Iterable of (personsName, phoneNumber, date, action)
+               """
+               try:
+                       recentCallsPage = self._browser.download(GCDialer._inboxallURL)
+               except urllib2.URLError, e:
+                       warnings.warn("%s is not accesible" % GCDialer._inboxallURL, UserWarning, 2)
+                       return
+
+               for match in self._inboxRe.finditer(recentCallsPage):
+                       phoneNumber = match.group(4)
+                       action = match.group(1)
+                       date = match.group(2)
+                       personsName = match.group(3)
+                       yield personsName, phoneNumber, date, action
+
+       def get_addressbooks(self):
+               """
+               @returns Iterable of (Address Book Factory, Book Id, Book Name)
+               """
+               yield self, None, "Grand Central"
+       
+       def open_addressbook(self, bookId):
+               return self
+
+       def get_contacts(self):
+               """
+               @returns Iterable of (contact id, contact name)
+               """
+               contactsPagesUrls = [GCDialer._contactsURL]
+               for contactsPageUrl in contactsPagesUrls:
+                       contactsPage = self._browser.download(contactsPageUrl)
+                       for contact_match in self._contactsRe.finditer(contactsPage):
+                               contactId = contact_match.group(1)
+                               contactName = contact_match.group(2)
+                               yield contactId, contactName
+
+                       next_match = self._contactsNextRe.match(contactsPage)
+                       if next_match is not None:
+                               newContactsPageUrl = self._contactsURL + next_match.group(1)
+                               contactsPagesUrls.append(newContactsPageUrl)
+       
+       def get_contact_details(self, contactId):
+               """
+               @returns Iterable of (Phone Type, Phone Number)
+               """
+               detailPage = self._browser.download(GCDialer._contactDetailURL + '/' + contactId)
+               for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
+                       phoneType = detail_match.group(1)
+                       phoneNumber = detail_match.group(2)
+                       yield (phoneType, phoneNumber)
+
+       def _grab_token(self, data):
+               "Pull the magic cookie from the datastream"
+               atGroup = GCDialer._accessTokenRe.search(data)
+               self._accessToken = atGroup.group(1)
+
+               anGroup = GCDialer._accountNumRe.search(data)
+               self._accountNum = anGroup.group(1)
+
+               self._callbackNumbers = {}
+               for match in GCDialer._callbackRe.finditer(data):
+                       self._callbackNumbers[match.group(1)] = match.group(2)
index 9c0df9b..c8c7c43 100755 (executable)
@@ -63,7 +63,8 @@ except ImportError:
        doctest = None
        optparse = None
 
-from gcbackend import GCDialer
+from gc_backend import GCDialer
+from evo_backend import EvolutionAddressBook
 
 import socket
 
diff --git a/src/gcbackend.py b/src/gcbackend.py
deleted file mode 100644 (file)
index 512cd0d..0000000
+++ /dev/null
@@ -1,298 +0,0 @@
-#!/usr/bin/python
-
-# GC Dialer - 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 Dialer backend code
-"""
-
-
-import os
-import re
-import urllib
-import urllib2
-import time
-import warnings
-
-from browser_emu import MozillaEmulator
-
-
-class GCDialer(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)
-       _accessTokenRe = re.compile(r"""<input type="hidden" name="a_t" [^>]*value="(.*)"/>""")
-       _isLoginPageRe = re.compile(r"""<form method="post" action="https://www.grandcentral.com/mobile/account/login">""")
-       _callbackRe = re.compile(r"""name="default_number" value="(\d+)" />\s+(.*)\s$""", re.M)
-       _accountNumRe = re.compile(r"""<img src="/images/mobile/inbox_logo.gif" alt="GrandCentral" />\s*(.{14})\s*&nbsp""", re.M)
-       _inboxRe = re.compile(r"""<td>.*?(voicemail|received|missed|call return).*?</td>\s+<td>\s+<font size="2">\s+(.*?)\s+&nbsp;\|&nbsp;\s+<a href="/mobile/contacts/.*?">(.*?)\s?</a>\s+<br/>\s+(.*?)\s?<a href=""", re.S)
-       _contactsRe = re.compile(r"""<a href="/mobile/contacts/detail/(\d+)">(.*?)</a>""", re.S)
-       _contactsNextRe = re.compile(r""".*<a href="/mobile/contacts(\?page=\d+)">Next</a>""", re.S)
-       _contactDetailGroupRe   = re.compile(r"""Group:\s*(\w*)""", re.S)
-       _contactDetailPhoneRe   = re.compile(r"""(\w+):[0-9\-\(\) \t]*?<a href="/mobile/calls/click_to_call\?destno=(\d+).*?">call</a>""", re.S)
-
-       _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"
-
-       def __init__(self, cookieFile = None):
-               # Important items in this function are the setup of the browser emulation and cookie file
-               self._msg = ""
-
-               self._browser = MozillaEmulator(None, 0)
-               if cookieFile is None:
-                       cookieFile = os.path.join(os.path.expanduser("~"), ".gc_dialer_cookies.txt")
-               self._browser.cookies.filename = cookieFile
-               if os.path.isfile(cookieFile):
-                       self._browser.cookies.load()
-
-               self._accessToken = None
-               self._accountNum = None
-               self._callbackNumbers = {}
-               self._lastAuthed = 0.0
-
-       def is_authed(self, force = False):
-               """
-               Attempts to detect a current session and pull the auth token ( a_t ) from the page.
-               @note Once logged in try not to reauth more than once a minute.
-               @returns If authenticated
-               """
-
-               if time.time() - self._lastAuthed < 60 and not force:
-                       return True
-
-               try:
-                       forwardSelectionPage = self._browser.download(GCDialer._forwardselectURL)
-               except urllib2.URLError, e:
-                       warnings.warn("%s is not accesible" % GCDialer._forwardselectURL, UserWarning, 2)
-                       return False
-
-               self._browser.cookies.save()
-               if GCDialer._isLoginPageRe.search(forwardSelectionPage) is None:
-                       self._grab_token(forwardSelectionPage)
-                       self._lastAuthed = time.time()
-                       return True
-
-               return False
-
-       def login(self, username, password):
-               """
-               Attempt to login to grandcentral
-               @returns Whether login was successful or not
-               """
-               if self.is_authed():
-                       return True
-
-               loginPostData = urllib.urlencode( {'username' : username , 'password' : password } )
-
-               try:
-                       loginSuccessOrFailurePage = self._browser.download(GCDialer._loginURL, loginPostData)
-               except urllib2.URLError, e:
-                       warnings.warn("%s is not accesible" % GCDialer._loginURL, UserWarning, 2)
-                       return False
-
-               return self.is_authed()
-
-       def logout(self):
-               self._lastAuthed = 0.0
-               self._browser.cookies.clear()
-               self._browser.cookies.save()
-
-       def dial(self, number):
-               """
-               This is the main function responsible for initating the callback
-               """
-               self._msg = ""
-
-               # If the number is not valid throw exception
-               if not self.is_valid_syntax(number):
-                       raise ValueError('number is not valid')
-
-               # No point if we don't have the magic cookie
-               if not self.is_authed():
-                       self._msg = "Not authenticated"
-                       return False
-
-               # Strip leading 1 from 11 digit dialing
-               if len(number) == 11 and number[0] == 1:
-                       number = number[1:]
-
-               try:
-                       callSuccessPage = self._browser.download(
-                               GCDialer._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)
-                       return False
-
-               if GCDialer._gcDialingStrRe.search(callSuccessPage) is not None:
-                       return True
-               else:
-                       self._msg = "Grand Central returned an error"
-                       return False
-
-               self._msg = "Unknown Error"
-               return False
-
-       def clear_caches(self):
-               pass
-
-       def is_valid_syntax(self, number):
-               """
-               @returns If This number be called ( syntax validation only )
-               """
-               return self._validateRe.match(number) is not None
-
-       def get_account_number(self):
-               """
-               @returns The grand central phone number
-               """
-               return self._accountNum
-
-       def set_sane_callback(self):
-               """
-               Try to set a sane default callback number on these preferences
-               1) 1747 numbers ( Gizmo )
-               2) anything with gizmo in the name
-               3) anything with computer in the name
-               4) the first value
-               """
-               numbers = self.get_callback_numbers()
-
-               for number, description in numbers.iteritems():
-                       if not re.compile(r"""1747""").match(number) is 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:
-                               self.set_callback_number(number)
-                               return
-
-               for number, description in numbers.iteritems():
-                       if not re.compile(r"""computer""", re.I).search(description) is None:
-                               self.set_callback_number(number)
-                               return
-
-               for number, description in numbers.iteritems():
-                       self.set_callback_number(number)
-                       return
-
-       def get_callback_numbers(self):
-               """
-               @returns a dictionary mapping call back numbers to descriptions
-               @note These results are cached for 30 minutes.
-               """
-               if time.time() - self._lastAuthed < 1800 or self.is_authed():
-                       return self._callbackNumbers
-
-               return {}
-
-       def set_callback_number(self, callbacknumber):
-               """
-               Set the number that grandcental calls
-               @param callbacknumber should be a proper 10 digit number
-               """
-               callbackPostData = urllib.urlencode({
-                       'a_t': self._accessToken,
-                       'default_number': callbacknumber
-               })
-               try:
-                       callbackSetPage = self._browser.download(GCDialer._setforwardURL, callbackPostData)
-               except urllib2.URLError, e:
-                       warnings.warn("%s is not accesible" % GCDialer._setforwardURL, UserWarning, 2)
-                       return False
-
-               self._browser.cookies.save()
-               return True
-
-       def get_callback_number(self):
-               """
-               @returns Current callback number or None
-               """
-               for c in self._browser.cookies:
-                       if c.name == "pda_forwarding_number":
-                               return c.value
-               return None
-
-       def get_recent(self):
-               """
-               @returns Iterable of (personsName, phoneNumber, date, action)
-               """
-               try:
-                       recentCallsPage = self._browser.download(GCDialer._inboxallURL)
-               except urllib2.URLError, e:
-                       warnings.warn("%s is not accesible" % GCDialer._inboxallURL, UserWarning, 2)
-                       return
-
-               for match in self._inboxRe.finditer(recentCallsPage):
-                       phoneNumber = match.group(4)
-                       action = match.group(1)
-                       date = match.group(2)
-                       personsName = match.group(3)
-                       yield personsName, phoneNumber, date, action
-
-       def get_contacts(self):
-               """
-               @returns Iterable of (contact id, contact name)
-               """
-               contactsPagesUrls = [GCDialer._contactsURL]
-               for contactsPageUrl in contactsPagesUrls:
-                       contactsPage = self._browser.download(contactsPageUrl)
-                       for contact_match in self._contactsRe.finditer(contactsPage):
-                               contactId = contact_match.group(1)
-                               contactName = contact_match.group(2)
-                               yield contactId, contactName
-
-                       next_match = self._contactsNextRe.match(contactsPage)
-                       if next_match is not None:
-                               newContactsPageUrl = self._contactsURL + next_match.group(1)
-                               contactsPagesUrls.append(newContactsPageUrl)
-       
-       def get_contact_details(self, contactId):
-               """
-               @returns Iterable of (Phone Type, Phone Number)
-               """
-               detailPage = self._browser.download(GCDialer._contactDetailURL + '/' + contactId)
-               for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
-                       phoneType = detail_match.group(1)
-                       phoneNumber = detail_match.group(2)
-                       yield (phoneType, phoneNumber)
-
-       def _grab_token(self, data):
-               "Pull the magic cookie from the datastream"
-               atGroup = GCDialer._accessTokenRe.search(data)
-               self._accessToken = atGroup.group(1)
-
-               anGroup = GCDialer._accountNumRe.search(data)
-               self._accountNum = anGroup.group(1)
-
-               self._callbackNumbers = {}
-               for match in GCDialer._callbackRe.finditer(data):
-                       self._callbackNumbers[match.group(1)] = match.group(2)