-
Notifications
You must be signed in to change notification settings - Fork 0
/
voice.py
265 lines (229 loc) · 9.16 KB
/
voice.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
from conf import config
from util import *
import settings
import os
#if settings.DEBUG:
# import logging
# logging.basicConfig()
# log = logging.getLogger('PyGoogleVoice')
# log.setLevel(logging.DEBUG)
#else:
log = None
class Voice(object):
"""
Main voice instance for interacting with the Google Voice service
Handles login/logout and most of the baser HTTP methods
"""
def __init__(self):
install_opener(build_opener(HTTPCookieProcessor(CookieJar())))
for name in settings.FEEDS:
setattr(self, name, self.__get_xml_page(name))
######################
# Some handy methods
######################
def special(self):
"""
Returns special identifier for your session (if logged in)
"""
if hasattr(self, '_special') and getattr(self, '_special'):
return self._special
try:
try:
regex = bytes("('_rnr_se':) '(.+)'", 'utf8')
except TypeError:
regex = bytes("('_rnr_se':) '(.+)'")
except NameError:
regex = r"('_rnr_se':) '(.+)'"
try:
response = self.__do_page('inbox', data=None, headers = {'Authorization': 'GoogleLogin auth=%s' % self._auth })
sp = re.search(regex, response.read()).group(2)
except AttributeError:
sp = None
self._special = sp
return sp
special = property(special)
def login(self, email=None, passwd=None):
"""
Login to the service using your Google Voice account
Credentials will be propmpted for if not given as args or in the ``~/.gvoice`` config file
"""
if hasattr(self, '_special') and getattr(self, '_special'):
return self
if email is None:
email = config.email
if email is None:
# email = input('Email address: ')
email="[email protected]"
if passwd is None:
passwd = config.password
if passwd is None:
# from getpass import getpass
# passwd = getpass()
passwd="dummy"
response = self.__do_page('login', {'accountType': 'HOSTED_OR_GOOGLE' ,'Email': email, 'Passwd': passwd, 'service': 'grandcentral' })
respMap = dict( line.strip().split('=', 1) for line in response.readlines() )
response.close()
if respMap.has_key('Auth'):
self._auth = respMap['Auth']
elif respMap.has_key('Error'):
if respMap['Error'] == 'CaptchaRequired':
raise LoginError('Clear the captcha at: %s' % respMap['Url'])
raise LoginError(respMap['Error'])
del email, passwd, respMap
try:
assert self.special
except (AssertionError, AttributeError):
raise LoginError
return self
def logout(self):
"""
Logs out an instance and makes sure it does not still have a session
"""
self.__do_page('logout')
if hasattr(self,"_auth"):
del self._auth
if self.special != None:
del self._special
assert self.special == None
return self
def call(self, outgoingNumber, forwardingNumber=None, phoneType=None, subscriberNumber=None):
"""
Make a call to an ``outgoingNumber`` from your ``forwardingNumber`` (optional).
If you pass in your ``forwardingNumber``, please also pass in the correct ``phoneType``
"""
if forwardingNumber is None:
forwardingNumber = config.forwardingNumber
if phoneType is None:
phoneType = config.phoneType
self.__validate_special_page('call', {
'outgoingNumber': outgoingNumber,
'forwardingNumber': forwardingNumber,
'subscriberNumber': subscriberNumber or 'undefined',
'phoneType': phoneType,
'remember': '1'
})
__call__ = call
def cancel(self, outgoingNumber=None, forwardingNumber=None):
"""
Cancels a call matching outgoing and forwarding numbers (if given).
Will raise an error if no matching call is being placed
"""
self.__validate_special_page('cancel', {
'outgoingNumber': outgoingNumber or 'undefined',
'forwardingNumber': forwardingNumber or 'undefined',
'cancelType': 'C2C',
})
def phones(self):
"""
Returns a list of ``Phone`` instances attached to your account.
"""
return [Phone(self, data) for data in self.contacts['phones'].values()]
phones = property(phones)
def settings(self):
"""
Dict of current Google Voice settings
"""
return AttrDict(self.contacts['settings'])
settings = property(settings)
def send_sms(self, phoneNumber, text):
"""
Send an SMS message to a given ``phoneNumber`` with the given ``text`` message
"""
self.__validate_special_page('sms', {'phoneNumber': phoneNumber, 'text': text})
def search(self, query):
"""
Search your Google Voice Account history for calls, voicemails, and sms
Returns ``Folder`` instance containting matching messages
"""
return self.__get_xml_page('search', data='?q=%s' % quote(query))()
def download(self, msg, adir=None):
"""
Download a voicemail or recorded call MP3 matching the given ``msg``
which can either be a ``Message`` instance, or a SHA1 identifier.
Saves files to ``adir`` (defaults to current directory).
Message hashes can be found in ``self.voicemail().messages`` for example.
Returns location of saved file.
"""
from os import path,getcwd
if isinstance(msg, Message):
msg = msg.id
assert is_sha1(msg), 'Message id not a SHA1 hash'
if adir is None:
adir = getcwd()
try:
response = self.__do_page('download', msg)
except:
raise DownloadError
fn = path.join(adir, '%s.mp3' % msg)
fo = open(fn, 'wb')
fo.write(response.read())
fo.close()
return fn
def contacts(self):
"""
Partial data of your Google Account Contacts related to your Voice account.
For a more comprehensive suite of APIs, check out https://code.google.com/apis/contacts/docs/1.0/developers_guide_python.html
"""
if hasattr(self, '_contacts'):
return self._contacts
self._contacts = self.__get_xml_page('contacts')()
return self._contacts
contacts = property(contacts)
######################
# Helper methods
######################
def __do_page(self, page, data=None, headers={}):
"""
Loads a page out of the settings and pass it on to urllib Request
"""
page = page.upper()
if isinstance(data, dict) or isinstance(data, tuple):
data = urlencode(data)
headers.update({'User-Agent': 'PyGoogleVoice/0.5'})
if log:
log.debug('%s?%s - %s' % (getattr(settings, page)[22:], data or '', headers))
if page in ('DOWNLOAD','XML_SEARCH'):
return urlopen(Request(getattr(settings, page) + data, None, headers))
if data:
headers.update({'Content-type': 'application/x-www-form-urlencoded;charset=utf-8'})
headers.update({'Content-length': '%d' % len(data)})
try:
return urlopen(Request(getattr(settings, page), data, headers))
except (HTTPError), e:
return e
def __validate_special_page(self, page, data={}, **kwargs):
"""
Validates a given special page for an 'ok' response
"""
data.update(kwargs)
load_and_validate(self.__do_special_page(page, data))
_Phone__validate_special_page = __validate_special_page
def __do_special_page(self, page, data=None, headers={}):
"""
Add self.special to request data
"""
assert self.special and self._auth, 'You must login before using this page'
if isinstance(data, tuple):
data += ('_rnr_se', self.special)
elif isinstance(data, dict):
data.update({'_rnr_se': self.special})
headers.update({'Authorization': 'GoogleLogin auth=%s' % self._auth})
return self.__do_page(page, data, headers)
_Phone__do_special_page = __do_special_page
def __get_xml_page(self, page, data=None, headers={}):
"""
Return XMLParser instance generated from given page
"""
return XMLParser(self, page, lambda: self.__do_special_page('XML_%s' % page.upper(), data, headers).read())
def __messages_post(self, page, *msgs, **kwargs):
"""
Performs message operations, eg deleting,staring,moving
"""
data = kwargs.items()
for msg in msgs:
if isinstance(msg, Message):
msg = msg.id
assert is_sha1(msg), 'Message id not a SHA1 hash'
data += (('messages',msg),)
return self.__do_special_page(page, dict(data))
_Message__messages_post = __messages_post