$search
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 # Some handy methods 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 # holy hackjob 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 # Helper methods 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