$search
00001 import re 00002 from sys import stdout 00003 from xml.parsers.expat import ParserCreate 00004 from time import gmtime 00005 from datetime import datetime 00006 from pprint import pprint 00007 try: 00008 from urllib2 import build_opener,install_opener, \ 00009 HTTPCookieProcessor,Request,urlopen 00010 from urllib import urlencode,quote 00011 except ImportError: 00012 from urllib.request import build_opener,install_opener, \ 00013 HTTPCookieProcessor,Request,urlopen 00014 from urllib.parse import urlencode,quote 00015 try: 00016 from http.cookiejar import LWPCookieJar as CookieJar 00017 except ImportError: 00018 from cookielib import LWPCookieJar as CookieJar 00019 try: 00020 from json import loads 00021 except ImportError: 00022 from simplejson import loads 00023 try: 00024 input = raw_input 00025 except NameError: 00026 input = input 00027 00028 sha1_re = re.compile(r'^[a-fA-F0-9]{40}$') 00029 00030 def print_(*values, **kwargs): 00031 """ 00032 Implementation of Python3's print function 00033 00034 Prints the values to a stream, or to sys.stdout by default. 00035 Optional keyword arguments: 00036 00037 file: a file-like object (stream); defaults to the current sys.stdout. 00038 sep: string inserted between values, default a space. 00039 end: string appended after the last value, default a newline. 00040 """ 00041 fo = kwargs.pop('file', stdout) 00042 fo.write(kwargs.pop('sep', ' ').join(map(str, values))) 00043 fo.write(kwargs.pop('end', '\n')) 00044 fo.flush() 00045 00046 def is_sha1(s): 00047 """ 00048 Returns ``True`` if the string is a SHA1 hash 00049 """ 00050 return bool(sha1_re.match(s)) 00051 00052 def validate_response(response): 00053 """ 00054 Validates that the JSON response is A-OK 00055 """ 00056 try: 00057 assert 'ok' in response and response['ok'] 00058 except AssertionError: 00059 raise ValidationError('There was a problem with GV: %s' % response) 00060 00061 def load_and_validate(response): 00062 """ 00063 Loads JSON data from http response then validates 00064 """ 00065 validate_response(loads(response.read())) 00066 00067 class ValidationError(Exception): 00068 """ 00069 Bombs when response code back from Voice 500s 00070 """ 00071 00072 class LoginError(Exception): 00073 """ 00074 Occurs when login credentials are incorrect 00075 """ 00076 00077 class ParsingError(Exception): 00078 """ 00079 Happens when XML feed parsing fails 00080 """ 00081 00082 class JSONError(Exception): 00083 """ 00084 Failed JSON deserialization 00085 """ 00086 00087 class DownloadError(Exception): 00088 """ 00089 Cannot download message, probably not in voicemail/recorded 00090 """ 00091 00092 class ForwardingError(Exception): 00093 """ 00094 Forwarding number given was incorrect 00095 """ 00096 00097 00098 class AttrDict(dict): 00099 def __getattr__(self, attr): 00100 if attr in self: 00101 return self[attr] 00102 00103 class Phone(AttrDict): 00104 """ 00105 Wrapper for phone objects used for phone specific methods 00106 Attributes are: 00107 00108 * id: int 00109 * phoneNumber: i18n phone number 00110 * formattedNumber: humanized phone number string 00111 * we: data dict 00112 * wd: data dict 00113 * verified: bool 00114 * name: strign label 00115 * smsEnabled: bool 00116 * scheduleSet: bool 00117 * policyBitmask: int 00118 * weekdayTimes: list 00119 * dEPRECATEDDisabled: bool 00120 * weekdayAllDay: bool 00121 * telephonyVerified 00122 * weekendTimes: list 00123 * active: bool 00124 * weekendAllDay: bool 00125 * enabledForOthers: bool 00126 * type: int (1 - Home, 2 - Mobile, 3 - Work, 4 - Gizmo) 00127 00128 """ 00129 def __init__(self, voice, data): 00130 self.voice = voice 00131 super(Phone, self).__init__(data) 00132 00133 def enable(self,): 00134 """ 00135 Enables this phone for usage 00136 """ 00137 return self.__call_forwarding() 00138 00139 def disable(self): 00140 """ 00141 Disables this phone 00142 """ 00143 return self.__call_forwarding('0') 00144 00145 def __call_forwarding(self, enabled='1'): 00146 """ 00147 Enables or disables this phone 00148 """ 00149 self.voice.__validate_special_page('default_forward', 00150 {'enabled':enabled, 'phoneId': self.id}) 00151 00152 def __str__(self): 00153 return self.phoneNumber 00154 00155 def __repr__(self): 00156 return '<Phone %s>' % self.phoneNumber 00157 00158 class Message(AttrDict): 00159 """ 00160 Wrapper for all call/sms message instances stored in Google Voice 00161 Attributes are: 00162 00163 * id: SHA1 identifier 00164 * isTrash: bool 00165 * displayStartDateTime: datetime 00166 * star: bool 00167 * isSpam: bool 00168 * startTime: gmtime 00169 * labels: list 00170 * displayStartTime: time 00171 * children: str 00172 * note: str 00173 * isRead: bool 00174 * displayNumber: str 00175 * relativeStartTime: str 00176 * phoneNumber: str 00177 * type: int 00178 00179 """ 00180 def __init__(self, folder, id, data): 00181 assert is_sha1(id), 'Message id not a SHA1 hash' 00182 self.folder = folder 00183 self.id = id 00184 super(AttrDict, self).__init__(data) 00185 self['startTime'] = gmtime(int(self['startTime'])/1000) 00186 self['displayStartDateTime'] = datetime.strptime( 00187 self['displayStartDateTime'], '%m/%d/%y %I:%M %p') 00188 self['displayStartTime'] = self['displayStartDateTime'].time() 00189 00190 def delete(self, trash=1): 00191 """ 00192 Moves this message to the Trash. Use ``message.delete(0)`` to move it out of the Trash. 00193 """ 00194 self.folder.voice.__messages_post('delete', self.id, trash=trash) 00195 00196 def star(self, star=1): 00197 """ 00198 Star this message. Use ``message.star(0)`` to unstar it. 00199 """ 00200 self.folder.voice.__messages_post('star', self.id, star=star) 00201 00202 def mark(self, read=1): 00203 """ 00204 Mark this message as read. Use ``message.mark(0)`` to mark it as unread. 00205 """ 00206 self.folder.voice.__messages_post('mark', self.id, read=read) 00207 00208 def download(self, adir=None): 00209 """ 00210 Download the message MP3 (if any). 00211 Saves files to ``adir`` (defaults to current directory). 00212 Message hashes can be found in ``self.voicemail().messages`` for example. 00213 Returns location of saved file. 00214 """ 00215 return self.folder.voice.download(self, adir) 00216 00217 def __str__(self): 00218 return self.id 00219 00220 def __repr__(self): 00221 return '<Message #%s (%s)>' % (self.id, self.phoneNumber) 00222 00223 class Folder(AttrDict): 00224 """ 00225 Folder wrapper for feeds from Google Voice 00226 Attributes are: 00227 00228 * totalSize: int (aka ``__len__``) 00229 * unreadCounts: dict 00230 * resultsPerPage: int 00231 * messages: list of Message instances 00232 """ 00233 def __init__(self, voice, name, data): 00234 self.voice = voice 00235 self.name = name 00236 super(AttrDict, self).__init__(data) 00237 00238 def messages(self): 00239 """ 00240 Returns a list of all messages in this folder 00241 """ 00242 return [Message(self, *i) for i in self['messages'].items()] 00243 messages = property(messages) 00244 00245 def __len__(self): 00246 return self['totalSize'] 00247 00248 def __repr__(self): 00249 return '<Folder %s (%s)>' % (self.name, len(self)) 00250 00251 class XMLParser(object): 00252 """ 00253 XML Parser helper that can dig json and html out of the feeds. 00254 The parser takes a ``Voice`` instance, page name, and function to grab data from. 00255 Calling the parser calls the data function once, sets up the ``json`` and ``html`` 00256 attributes and returns a ``Folder`` instance for the given page:: 00257 00258 >>> o = XMLParser(voice, 'voicemail', lambda: 'some xml payload') 00259 >>> o() 00260 ... <Folder ...> 00261 >>> o.json 00262 ... 'some json payload' 00263 >>> o.data 00264 ... 'loaded json payload' 00265 >>> o.html 00266 ... 'some html payload' 00267 00268 """ 00269 attr = None 00270 00271 def start_element(self, name, attrs): 00272 if name in ('json','html'): 00273 self.attr = name 00274 def end_element(self, name): self.attr = None 00275 def char_data(self, data): 00276 if self.attr and data: 00277 setattr(self, self.attr, getattr(self, self.attr) + data) 00278 00279 def __init__(self, voice, name, datafunc): 00280 self.json, self.html = '','' 00281 self.datafunc = datafunc 00282 self.voice = voice 00283 self.name = name 00284 00285 def __call__(self): 00286 self.json, self.html = '','' 00287 parser = ParserCreate() 00288 parser.StartElementHandler = self.start_element 00289 parser.EndElementHandler = self.end_element 00290 parser.CharacterDataHandler = self.char_data 00291 try: 00292 data = self.datafunc() 00293 parser.Parse(data, 1) 00294 except: 00295 raise ParsingError 00296 return self.folder 00297 00298 def folder(self): 00299 """ 00300 Returns associated ``Folder`` instance for given page (``self.name``) 00301 """ 00302 return Folder(self.voice, self.name, self.data) 00303 folder = property(folder) 00304 00305 def data(self): 00306 """ 00307 Returns the parsed json information after calling the XMLParser 00308 """ 00309 try: 00310 return loads(self.json) 00311 except: 00312 raise JSONError 00313 data = property(data) 00314