00001 from conf import config
00002 from util import *
00003 import settings
00004 import os
00005
00006 if settings.DEBUG:
00007 import logging
00008 logging.basicConfig()
00009 log = logging.getLogger('PyGoogleVoice')
00010 log.setLevel(logging.DEBUG)
00011 else:
00012 log = None
00013
00014 class Voice(object):
00015 """
00016 Main voice instance for interacting with the Google Voice service
00017 Handles login/logout and most of the baser HTTP methods
00018 """
00019 def __init__(self):
00020 install_opener(build_opener(HTTPCookieProcessor(CookieJar())))
00021
00022 for name in settings.FEEDS:
00023 setattr(self, name, self.__get_xml_page(name))
00024
00025
00026
00027
00028 def special(self):
00029 """
00030 Returns special identifier for your session (if logged in)
00031 """
00032 if hasattr(self, '_special') and getattr(self, '_special'):
00033 return self._special
00034 try:
00035 try:
00036 regex = bytes("('_rnr_se':) '(.+)'", 'utf8')
00037 except TypeError:
00038 regex = bytes("('_rnr_se':) '(.+)'")
00039 except NameError:
00040 regex = r"('_rnr_se':) '(.+)'"
00041 try:
00042 sp = re.search(regex, urlopen(settings.INBOX).read()).group(2)
00043 except AttributeError:
00044 sp = None
00045 self._special = sp
00046 return sp
00047 special = property(special)
00048
00049 def login(self, email=None, passwd=None):
00050 """
00051 Login to the service using your Google Voice account
00052 Credentials will be propmpted for if not given as args or in the ``~/.gvoice`` config file
00053 """
00054 if hasattr(self, '_special') and getattr(self, '_special'):
00055 return self
00056
00057 if email is None:
00058 email = config.email
00059 if email is None:
00060 email = input('Email address: ')
00061
00062 if passwd is None:
00063 passwd = config.password
00064 if passwd is None:
00065 from getpass import getpass
00066 passwd = getpass()
00067
00068 content = self.__do_page('login').read()
00069
00070 galx = re.search(r"name=\"GALX\"\s+value=\"(.+)\"", content).group(1)
00071 self.__do_page('login', {'Email': email, 'Passwd': passwd, 'GALX': galx})
00072
00073 del email, passwd
00074
00075 try:
00076 assert self.special
00077 except (AssertionError, AttributeError):
00078 raise LoginError
00079
00080 return self
00081
00082 def logout(self):
00083 """
00084 Logs out an instance and makes sure it does not still have a session
00085 """
00086 self.__do_page('logout')
00087 del self._special
00088 assert self.special == None
00089 return self
00090
00091 def call(self, outgoingNumber, forwardingNumber=None, phoneType=None, subscriberNumber=None):
00092 """
00093 Make a call to an ``outgoingNumber`` from your ``forwardingNumber`` (optional).
00094 If you pass in your ``forwardingNumber``, please also pass in the correct ``phoneType``
00095 """
00096 if forwardingNumber is None:
00097 forwardingNumber = config.forwardingNumber
00098 if phoneType is None:
00099 phoneType = config.phoneType
00100
00101 self.__validate_special_page('call', {
00102 'outgoingNumber': outgoingNumber,
00103 'forwardingNumber': forwardingNumber,
00104 'subscriberNumber': subscriberNumber or 'undefined',
00105 'phoneType': phoneType,
00106 'remember': '1'
00107 })
00108
00109 __call__ = call
00110
00111 def cancel(self, outgoingNumber=None, forwardingNumber=None):
00112 """
00113 Cancels a call matching outgoing and forwarding numbers (if given).
00114 Will raise an error if no matching call is being placed
00115 """
00116 self.__validate_special_page('cancel', {
00117 'outgoingNumber': outgoingNumber or 'undefined',
00118 'forwardingNumber': forwardingNumber or 'undefined',
00119 'cancelType': 'C2C',
00120 })
00121
00122 def phones(self):
00123 """
00124 Returns a list of ``Phone`` instances attached to your account.
00125 """
00126 return [Phone(self, data) for data in self.contacts['phones'].values()]
00127 phones = property(phones)
00128
00129 def settings(self):
00130 """
00131 Dict of current Google Voice settings
00132 """
00133 return AttrDict(self.contacts['settings'])
00134 settings = property(settings)
00135
00136 def send_sms(self, phoneNumber, text):
00137 """
00138 Send an SMS message to a given ``phoneNumber`` with the given ``text`` message
00139 """
00140 self.__validate_special_page('sms', {'phoneNumber': phoneNumber, 'text': text})
00141
00142 def search(self, query):
00143 """
00144 Search your Google Voice Account history for calls, voicemails, and sms
00145 Returns ``Folder`` instance containting matching messages
00146 """
00147 return self.__get_xml_page('search', data='?q=%s' % quote(query))()
00148
00149 def download(self, msg, adir=None):
00150 """
00151 Download a voicemail or recorded call MP3 matching the given ``msg``
00152 which can either be a ``Message`` instance, or a SHA1 identifier.
00153 Saves files to ``adir`` (defaults to current directory).
00154 Message hashes can be found in ``self.voicemail().messages`` for example.
00155 Returns location of saved file.
00156 """
00157 from os import path,getcwd
00158 if isinstance(msg, Message):
00159 msg = msg.id
00160 assert is_sha1(msg), 'Message id not a SHA1 hash'
00161 if adir is None:
00162 adir = getcwd()
00163 try:
00164 response = self.__do_page('download', msg)
00165 except:
00166 raise DownloadError
00167 fn = path.join(adir, '%s.mp3' % msg)
00168 fo = open(fn, 'wb')
00169 fo.write(response.read())
00170 fo.close()
00171 return fn
00172
00173 def contacts(self):
00174 """
00175 Partial data of your Google Account Contacts related to your Voice account.
00176 For a more comprehensive suite of APIs, check out http://code.google.com/apis/contacts/docs/1.0/developers_guide_python.html
00177 """
00178 if hasattr(self, '_contacts'):
00179 return self._contacts
00180 self._contacts = self.__get_xml_page('contacts')()
00181 return self._contacts
00182 contacts = property(contacts)
00183
00184
00185
00186
00187
00188
00189 def __do_page(self, page, data=None, headers={}):
00190 """
00191 Loads a page out of the settings and pass it on to urllib Request
00192 """
00193 page = page.upper()
00194 if isinstance(data, dict) or isinstance(data, tuple):
00195 data = urlencode(data)
00196 headers.update({'User-Agent': 'PyGoogleVoice/0.5'})
00197 if log:
00198 log.debug('%s?%s - %s' % (getattr(settings, page)[22:], data or '', headers))
00199 if page in ('DOWNLOAD','XML_SEARCH'):
00200 return urlopen(Request(getattr(settings, page) + data, None, headers))
00201 if data:
00202 headers.update({'Content-type': 'application/x-www-form-urlencoded;charset=utf-8'})
00203 return urlopen(Request(getattr(settings, page), data, headers))
00204
00205 def __validate_special_page(self, page, data={}, **kwargs):
00206 """
00207 Validates a given special page for an 'ok' response
00208 """
00209 data.update(kwargs)
00210 load_and_validate(self.__do_special_page(page, data))
00211
00212 _Phone__validate_special_page = __validate_special_page
00213
00214 def __do_special_page(self, page, data=None, headers={}):
00215 """
00216 Add self.special to request data
00217 """
00218 assert self.special, 'You must login before using this page'
00219 if isinstance(data, tuple):
00220 data += ('_rnr_se', self.special)
00221 elif isinstance(data, dict):
00222 data.update({'_rnr_se': self.special})
00223 return self.__do_page(page, data, headers)
00224
00225 _Phone__do_special_page = __do_special_page
00226
00227 def __get_xml_page(self, page, data=None, headers={}):
00228 """
00229 Return XMLParser instance generated from given page
00230 """
00231 return XMLParser(self, page, lambda: self.__do_special_page('XML_%s' % page.upper(), data, headers).read())
00232
00233 def __messages_post(self, page, *msgs, **kwargs):
00234 """
00235 Performs message operations, eg deleting,staring,moving
00236 """
00237 data = kwargs.items()
00238 for msg in msgs:
00239 if isinstance(msg, Message):
00240 msg = msg.id
00241 assert is_sha1(msg), 'Message id not a SHA1 hash'
00242 data += (('messages',msg),)
00243 return self.__do_special_page(page, dict(data))
00244
00245 _Message__messages_post = __messages_post