a20eef80c932ef1454dc9392a2e79c9fd141de31
[theonering] / src / gvoice / dialer.py
1 #!/usr/bin/python
2
3 """
4 DialCentral - Front end for Google's GoogleVoice service.
5 Copyright (C) 2008  Eric Warnke ericew AT gmail DOT com
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20
21 Google Voice backend code
22
23 Resources
24         http://thatsmith.com/2009/03/google-voice-addon-for-firefox/
25         http://posttopic.com/topic/google-voice-add-on-development
26 """
27
28
29 import os
30 import re
31 import urllib
32 import urllib2
33 import time
34 import datetime
35 import itertools
36 import logging
37 from xml.sax import saxutils
38
39 from xml.etree import ElementTree
40
41 import browser_emu
42
43 try:
44         import simplejson
45 except ImportError:
46         simplejson = None
47
48
49 _moduleLogger = logging.getLogger("gvoice.dialer")
50 _TRUE_REGEX = re.compile("true")
51 _FALSE_REGEX = re.compile("false")
52
53
54 def safe_eval(s):
55         s = _TRUE_REGEX.sub("True", s)
56         s = _FALSE_REGEX.sub("False", s)
57         return eval(s, {}, {})
58
59
60 if simplejson is None:
61         def parse_json(flattened):
62                 return safe_eval(flattened)
63 else:
64         def parse_json(flattened):
65                 return simplejson.loads(flattened)
66
67
68 def itergroup(iterator, count, padValue = None):
69         """
70         Iterate in groups of 'count' values. If there
71         aren't enough values, the last result is padded with
72         None.
73
74         >>> for val in itergroup([1, 2, 3, 4, 5, 6], 3):
75         ...     print tuple(val)
76         (1, 2, 3)
77         (4, 5, 6)
78         >>> for val in itergroup([1, 2, 3, 4, 5, 6], 3):
79         ...     print list(val)
80         [1, 2, 3]
81         [4, 5, 6]
82         >>> for val in itergroup([1, 2, 3, 4, 5, 6, 7], 3):
83         ...     print tuple(val)
84         (1, 2, 3)
85         (4, 5, 6)
86         (7, None, None)
87         >>> for val in itergroup("123456", 3):
88         ...     print tuple(val)
89         ('1', '2', '3')
90         ('4', '5', '6')
91         >>> for val in itergroup("123456", 3):
92         ...     print repr("".join(val))
93         '123'
94         '456'
95         """
96         paddedIterator = itertools.chain(iterator, itertools.repeat(padValue, count-1))
97         nIterators = (paddedIterator, ) * count
98         return itertools.izip(*nIterators)
99
100
101 class GVDialer(object):
102         """
103         This class encapsulates all of the knowledge necessary to interact with the GoogleVoice servers
104         the functions include login, setting up a callback number, and initalting a callback
105         """
106
107         def __init__(self, cookieFile = None):
108                 # Important items in this function are the setup of the browser emulation and cookie file
109                 self._browser = browser_emu.MozillaEmulator(1)
110                 if cookieFile is None:
111                         cookieFile = os.path.join(os.path.expanduser("~"), ".gv_cookies.txt")
112                 self._browser.cookies.filename = cookieFile
113                 if os.path.isfile(cookieFile):
114                         self._browser.cookies.load()
115
116                 self._token = ""
117                 self._accountNum = ""
118                 self._lastAuthed = 0.0
119                 self._callbackNumber = ""
120                 self._callbackNumbers = {}
121
122                 self.__contacts = None
123
124         def is_authed(self, force = False):
125                 """
126                 Attempts to detect a current session
127                 @note Once logged in try not to reauth more than once a minute.
128                 @returns If authenticated
129                 """
130
131                 if (time.time() - self._lastAuthed) < 120 and not force:
132                         return True
133
134                 try:
135                         page = self._browser.download(self._forwardURL)
136                         self._grab_account_info(page)
137                 except Exception, e:
138                         _moduleLogger.exception(str(e))
139                         return False
140
141                 self._browser.cookies.save()
142                 self._lastAuthed = time.time()
143                 return True
144
145         _loginURL = "https://www.google.com/accounts/ServiceLoginAuth"
146
147         def login(self, username, password):
148                 """
149                 Attempt to login to GoogleVoice
150                 @returns Whether login was successful or not
151                 """
152                 loginPostData = urllib.urlencode({
153                         'Email' : username,
154                         'Passwd' : password,
155                         'service': "grandcentral",
156                         "ltmpl": "mobile",
157                         "btmpl": "mobile",
158                         "PersistentCookie": "yes",
159                         "continue": self._forwardURL,
160                 })
161
162                 try:
163                         loginSuccessOrFailurePage = self._browser.download(self._loginURL, loginPostData)
164                 except urllib2.URLError, e:
165                         _moduleLogger.exception(str(e))
166                         raise RuntimeError("%s is not accesible" % self._loginURL)
167
168                 try:
169                         self._grab_account_info(loginSuccessOrFailurePage)
170                 except Exception, e:
171                         _moduleLogger.exception(str(e))
172                         return False
173
174                 self._browser.cookies.save()
175                 self._lastAuthed = time.time()
176                 return True
177
178         def logout(self):
179                 self._lastAuthed = 0.0
180                 self._browser.cookies.clear()
181                 self._browser.cookies.save()
182
183                 self.clear_caches()
184
185         _gvDialingStrRe = re.compile("This may take a few seconds", re.M)
186         _clicktocallURL = "https://www.google.com/voice/m/sendcall"
187
188         def dial(self, number):
189                 """
190                 This is the main function responsible for initating the callback
191                 """
192                 number = self._send_validation(number)
193                 try:
194                         clickToCallData = urllib.urlencode({
195                                 "number": number,
196                                 "phone": self._callbackNumber,
197                                 "_rnr_se": self._token,
198                         })
199                         otherData = {
200                                 'Referer' : 'https://google.com/voice/m/callsms',
201                         }
202                         callSuccessPage = self._browser.download(self._clicktocallURL, clickToCallData, None, otherData)
203                 except urllib2.URLError, e:
204                         _moduleLogger.exception(str(e))
205                         raise RuntimeError("%s is not accesible" % self._clicktocallURL)
206
207                 if self._gvDialingStrRe.search(callSuccessPage) is None:
208                         raise RuntimeError("Google Voice returned an error")
209
210                 return True
211
212         _sendSmsURL = "https://www.google.com/voice/m/sendsms"
213
214         def send_sms(self, number, message):
215                 number = self._send_validation(number)
216                 try:
217                         smsData = urllib.urlencode({
218                                 "number": number,
219                                 "smstext": message,
220                                 "_rnr_se": self._token,
221                                 "id": "undefined",
222                                 "c": "undefined",
223                         })
224                         otherData = {
225                                 'Referer' : 'https://google.com/voice/m/sms',
226                         }
227                         smsSuccessPage = self._browser.download(self._sendSmsURL, smsData, None, otherData)
228                 except urllib2.URLError, e:
229                         _moduleLogger.exception(str(e))
230                         raise RuntimeError("%s is not accesible" % self._sendSmsURL)
231
232                 return True
233
234         def clear_caches(self):
235                 self.__contacts = None
236
237         _validateRe = re.compile("^[0-9]{10,}$")
238
239         def is_valid_syntax(self, number):
240                 """
241                 @returns If This number be called ( syntax validation only )
242                 """
243                 return self._validateRe.match(number) is not None
244
245         def get_account_number(self):
246                 """
247                 @returns The GoogleVoice phone number
248                 """
249                 return self._accountNum
250
251         def get_callback_numbers(self):
252                 """
253                 @returns a dictionary mapping call back numbers to descriptions
254                 @note These results are cached for 30 minutes.
255                 """
256                 if not self.is_authed():
257                         return {}
258                 return self._callbackNumbers
259
260         _setforwardURL = "https://www.google.com//voice/m/setphone"
261
262         def set_callback_number(self, callbacknumber):
263                 """
264                 Set the number that GoogleVoice calls
265                 @param callbacknumber should be a proper 10 digit number
266                 """
267                 self._callbackNumber = callbacknumber
268                 return True
269
270         def get_callback_number(self):
271                 """
272                 @returns Current callback number or None
273                 """
274                 return self._callbackNumber
275
276         def get_recent(self):
277                 """
278                 @returns Iterable of (personsName, phoneNumber, date, action)
279                 """
280                 sortedRecent = [
281                         (exactDate, name, number, relativeDate, action)
282                         for (name, number, exactDate, relativeDate, action) in self._get_recent()
283                 ]
284                 sortedRecent.sort(reverse = True)
285                 for exactDate, name, number, relativeDate, action in sortedRecent:
286                         yield name, number, relativeDate, action
287
288         _contactsRe = re.compile(r"""<a href="/voice/m/contact/(\d+)">(.*?)</a>""", re.S)
289         _contactsNextRe = re.compile(r""".*<a href="/voice/m/contacts(\?p=\d+)">Next.*?</a>""", re.S)
290         _contactsURL = "https://www.google.com/voice/mobile/contacts"
291
292         def get_contacts(self):
293                 """
294                 @returns Iterable of (contact id, contact name)
295                 """
296                 if self.__contacts is None:
297                         self.__contacts = []
298
299                         contactsPagesUrls = [self._contactsURL]
300                         for contactsPageUrl in contactsPagesUrls:
301                                 try:
302                                         contactsPage = self._browser.download(contactsPageUrl)
303                                 except urllib2.URLError, e:
304                                         _moduleLogger.exception(str(e))
305                                         raise RuntimeError("%s is not accesible" % contactsPageUrl)
306                                 for contact_match in self._contactsRe.finditer(contactsPage):
307                                         contactId = contact_match.group(1)
308                                         contactName = saxutils.unescape(contact_match.group(2))
309                                         contact = contactId, contactName
310                                         self.__contacts.append(contact)
311                                         yield contact
312
313                                 next_match = self._contactsNextRe.match(contactsPage)
314                                 if next_match is not None:
315                                         newContactsPageUrl = self._contactsURL + next_match.group(1)
316                                         contactsPagesUrls.append(newContactsPageUrl)
317                 else:
318                         for contact in self.__contacts:
319                                 yield contact
320
321         _contactDetailPhoneRe = re.compile(r"""<div.*?>([0-9+\-\(\) \t]+?)<span.*?>\((\w+)\)</span>""", re.S)
322         _contactDetailURL = "https://www.google.com/voice/mobile/contact"
323
324         def get_contact_details(self, contactId):
325                 """
326                 @returns Iterable of (Phone Type, Phone Number)
327                 """
328                 try:
329                         detailPage = self._browser.download(self._contactDetailURL + '/' + contactId)
330                 except urllib2.URLError, e:
331                         _moduleLogger.exception(str(e))
332                         raise RuntimeError("%s is not accesible" % self._contactDetailURL)
333
334                 for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
335                         phoneNumber = detail_match.group(1)
336                         phoneType = saxutils.unescape(detail_match.group(2))
337                         yield (phoneType, phoneNumber)
338
339         _voicemailURL = "https://www.google.com/voice/inbox/recent/voicemail/"
340         _smsURL = "https://www.google.com/voice/inbox/recent/sms/"
341
342         def get_messages(self):
343                 try:
344                         voicemailPage = self._browser.download(self._voicemailURL)
345                 except urllib2.URLError, e:
346                         _moduleLogger.exception(str(e))
347                         raise RuntimeError("%s is not accesible" % self._voicemailURL)
348                 voicemailHtml = self._grab_html(voicemailPage)
349                 parsedVoicemail = self._parse_voicemail(voicemailHtml)
350                 decoratedVoicemails = self._decorate_voicemail(parsedVoicemail)
351
352                 try:
353                         smsPage = self._browser.download(self._smsURL)
354                 except urllib2.URLError, e:
355                         _moduleLogger.exception(str(e))
356                         raise RuntimeError("%s is not accesible" % self._smsURL)
357                 smsHtml = self._grab_html(smsPage)
358                 parsedSms = self._parse_sms(smsHtml)
359                 decoratedSms = self._decorate_sms(parsedSms)
360
361                 allMessages = itertools.chain(decoratedVoicemails, decoratedSms)
362                 return allMessages
363
364         def _grab_json(self, flatXml):
365                 xmlTree = ElementTree.fromstring(flatXml)
366                 jsonElement = xmlTree.getchildren()[0]
367                 flatJson = jsonElement.text
368                 jsonTree = parse_json(flatJson)
369                 return jsonTree
370
371         def _grab_html(self, flatXml):
372                 xmlTree = ElementTree.fromstring(flatXml)
373                 htmlElement = xmlTree.getchildren()[1]
374                 flatHtml = htmlElement.text
375                 return flatHtml
376
377         _tokenRe = re.compile(r"""<input.*?name="_rnr_se".*?value="(.*?)"\s*/>""")
378         _accountNumRe = re.compile(r"""<b class="ms\d">(.{14})</b></div>""")
379         _callbackRe = re.compile(r"""\s+(.*?):\s*(.*?)<br\s*/>\s*$""", re.M)
380         _forwardURL = "https://www.google.com/voice/mobile/phones"
381
382         def _grab_account_info(self, page):
383                 tokenGroup = self._tokenRe.search(page)
384                 if tokenGroup is None:
385                         raise RuntimeError("Could not extract authentication token from GoogleVoice")
386                 self._token = tokenGroup.group(1)
387
388                 anGroup = self._accountNumRe.search(page)
389                 if anGroup is not None:
390                         self._accountNum = anGroup.group(1)
391                 else:
392                         _moduleLogger.debug("Could not extract account number from GoogleVoice")
393
394                 self._callbackNumbers = {}
395                 for match in self._callbackRe.finditer(page):
396                         callbackNumber = match.group(2)
397                         callbackName = match.group(1)
398                         self._callbackNumbers[callbackNumber] = callbackName
399
400         def _send_validation(self, number):
401                 if not self.is_valid_syntax(number):
402                         raise ValueError('Number is not valid: "%s"' % number)
403                 elif not self.is_authed():
404                         raise RuntimeError("Not Authenticated")
405
406                 if len(number) == 11 and number[0] == 1:
407                         # Strip leading 1 from 11 digit dialing
408                         number = number[1:]
409                 return number
410
411         _recentCallsURL = "https://www.google.com/voice/inbox/recent/"
412         _placedCallsURL = "https://www.google.com/voice/inbox/recent/placed/"
413         _receivedCallsURL = "https://www.google.com/voice/inbox/recent/received/"
414         _missedCallsURL = "https://www.google.com/voice/inbox/recent/missed/"
415
416         def _get_recent(self):
417                 """
418                 @returns Iterable of (personsName, phoneNumber, exact date, relative date, action)
419                 """
420                 for action, url in (
421                         ("Received", self._receivedCallsURL),
422                         ("Missed", self._missedCallsURL),
423                         ("Placed", self._placedCallsURL),
424                 ):
425                         try:
426                                 flatXml = self._browser.download(url)
427                         except urllib2.URLError, e:
428                                 _moduleLogger.exception(str(e))
429                                 raise RuntimeError("%s is not accesible" % url)
430
431                         allRecentHtml = self._grab_html(flatXml)
432                         allRecentData = self._parse_voicemail(allRecentHtml)
433                         for recentCallData in allRecentData:
434                                 exactTime = recentCallData["time"]
435                                 if recentCallData["name"]:
436                                         header = recentCallData["name"]
437                                 elif recentCallData["prettyNumber"]:
438                                         header = recentCallData["prettyNumber"]
439                                 elif recentCallData["location"]:
440                                         header = recentCallData["location"]
441                                 else:
442                                         header = "Unknown"
443                                 yield header, recentCallData["number"], exactTime, recentCallData["relTime"], action
444
445         _seperateVoicemailsRegex = re.compile(r"""^\s*<div id="(\w+)"\s* class=".*?gc-message.*?">""", re.MULTILINE | re.DOTALL)
446         _exactVoicemailTimeRegex = re.compile(r"""<span class="gc-message-time">(.*?)</span>""", re.MULTILINE)
447         _relativeVoicemailTimeRegex = re.compile(r"""<span class="gc-message-relative">(.*?)</span>""", re.MULTILINE)
448         _voicemailNameRegex = re.compile(r"""<a class=.*?gc-message-name-link.*?>(.*?)</a>""", re.MULTILINE | re.DOTALL)
449         _voicemailNumberRegex = re.compile(r"""<input type="hidden" class="gc-text gc-quickcall-ac" value="(.*?)"/>""", re.MULTILINE)
450         _prettyVoicemailNumberRegex = re.compile(r"""<span class="gc-message-type">(.*?)</span>""", re.MULTILINE)
451         _voicemailLocationRegex = re.compile(r"""<span class="gc-message-location">.*?<a.*?>(.*?)</a></span>""", re.MULTILINE)
452         _messagesContactID = re.compile(r"""<a class=".*?gc-message-name-link.*?">.*?</a>\s*?<span .*?>(.*?)</span>""", re.MULTILINE)
453         #_voicemailMessageRegex = re.compile(r"""<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>""", re.MULTILINE)
454         #_voicemailMessageRegex = re.compile(r"""<a .*? class="gc-message-mni">(.*?)</a>""", re.MULTILINE)
455         _voicemailMessageRegex = re.compile(r"""(<span id="\d+-\d+" class="gc-word-(.*?)">(.*?)</span>|<a .*? class="gc-message-mni">(.*?)</a>)""", re.MULTILINE)
456
457         @staticmethod
458         def _interpret_voicemail_regex(group):
459                 quality, content, number = group.group(2), group.group(3), group.group(4)
460                 if quality is not None and content is not None:
461                         return quality, content
462                 elif number is not None:
463                         return "high", number
464
465         def _parse_voicemail(self, voicemailHtml):
466                 splitVoicemail = self._seperateVoicemailsRegex.split(voicemailHtml)
467                 for messageId, messageHtml in itergroup(splitVoicemail[1:], 2):
468                         exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
469                         exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
470                         exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p")
471                         relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
472                         relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
473                         locationGroup = self._voicemailLocationRegex.search(messageHtml)
474                         location = locationGroup.group(1).strip() if locationGroup else ""
475
476                         nameGroup = self._voicemailNameRegex.search(messageHtml)
477                         name = nameGroup.group(1).strip() if nameGroup else ""
478                         numberGroup = self._voicemailNumberRegex.search(messageHtml)
479                         number = numberGroup.group(1).strip() if numberGroup else ""
480                         prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml)
481                         prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
482                         contactIdGroup = self._messagesContactID.search(messageHtml)
483                         contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
484
485                         messageGroups = self._voicemailMessageRegex.finditer(messageHtml)
486                         messageParts = (
487                                 self._interpret_voicemail_regex(group)
488                                 for group in messageGroups
489                         ) if messageGroups else ()
490
491                         yield {
492                                 "id": messageId.strip(),
493                                 "contactId": contactId,
494                                 "name": name,
495                                 "time": exactTime,
496                                 "relTime": relativeTime,
497                                 "prettyNumber": prettyNumber,
498                                 "number": number,
499                                 "location": location,
500                                 "messageParts": messageParts,
501                         }
502
503         def _decorate_voicemail(self, parsedVoicemails):
504                 messagePartFormat = {
505                         "med1": "<i>%s</i>",
506                         "med2": "%s",
507                         "high": "<b>%s</b>",
508                 }
509                 for voicemailData in parsedVoicemails:
510                         message = " ".join((
511                                 messagePartFormat[quality] % part
512                                 for (quality, part) in voicemailData["messageParts"]
513                         )).strip()
514                         if not message:
515                                 message = "No Transcription"
516                         whoFrom = voicemailData["name"]
517                         when = voicemailData["time"]
518                         voicemailData["messageParts"] = ((whoFrom, message, when), )
519                         yield voicemailData
520
521         _smsFromRegex = re.compile(r"""<span class="gc-message-sms-from">(.*?)</span>""", re.MULTILINE | re.DOTALL)
522         _smsTextRegex = re.compile(r"""<span class="gc-message-sms-time">(.*?)</span>""", re.MULTILINE | re.DOTALL)
523         _smsTimeRegex = re.compile(r"""<span class="gc-message-sms-text">(.*?)</span>""", re.MULTILINE | re.DOTALL)
524
525         def _parse_sms(self, smsHtml):
526                 splitSms = self._seperateVoicemailsRegex.split(smsHtml)
527                 for messageId, messageHtml in itergroup(splitSms[1:], 2):
528                         exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
529                         exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
530                         exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p")
531                         relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
532                         relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
533
534                         nameGroup = self._voicemailNameRegex.search(messageHtml)
535                         name = nameGroup.group(1).strip() if nameGroup else ""
536                         numberGroup = self._voicemailNumberRegex.search(messageHtml)
537                         number = numberGroup.group(1).strip() if numberGroup else ""
538                         prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml)
539                         prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
540                         contactIdGroup = self._messagesContactID.search(messageHtml)
541                         contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
542
543                         fromGroups = self._smsFromRegex.finditer(messageHtml)
544                         fromParts = (group.group(1).strip() for group in fromGroups)
545                         textGroups = self._smsTextRegex.finditer(messageHtml)
546                         textParts = (group.group(1).strip() for group in textGroups)
547                         timeGroups = self._smsTimeRegex.finditer(messageHtml)
548                         timeParts = (group.group(1).strip() for group in timeGroups)
549
550                         messageParts = itertools.izip(fromParts, textParts, timeParts)
551
552                         yield {
553                                 "id": messageId.strip(),
554                                 "contactId": contactId,
555                                 "name": name,
556                                 "time": exactTime,
557                                 "relTime": relativeTime,
558                                 "prettyNumber": prettyNumber,
559                                 "number": number,
560                                 "location": "",
561                                 "messageParts": messageParts,
562                         }
563
564         def _decorate_sms(self, parsedTexts):
565                 return parsedTexts
566
567
568 def set_sane_callback(backend):
569         """
570         Try to set a sane default callback number on these preferences
571         1) 1747 numbers ( Gizmo )
572         2) anything with gizmo in the name
573         3) anything with computer in the name
574         4) the first value
575         """
576         numbers = backend.get_callback_numbers()
577
578         priorityOrderedCriteria = [
579                 ("1747", None),
580                 (None, "gizmo"),
581                 (None, "computer"),
582                 (None, "sip"),
583                 (None, None),
584         ]
585
586         for numberCriteria, descriptionCriteria in priorityOrderedCriteria:
587                 for number, description in numbers.iteritems():
588                         if numberCriteria is not None and re.compile(numberCriteria).match(number) is None:
589                                 continue
590                         if descriptionCriteria is not None and re.compile(descriptionCriteria).match(description) is None:
591                                 continue
592                         backend.set_callback_number(number)
593                         return
594
595
596 def sort_messages(allMessages):
597         sortableAllMessages = [
598                 (message["time"], message)
599                 for message in allMessages
600         ]
601         sortableAllMessages.sort(reverse=True)
602         return (
603                 message
604                 for (exactTime, message) in sortableAllMessages
605         )
606
607
608 def decorate_message(messageData):
609         exactTime = messageData["time"]
610         if messageData["name"]:
611                 header = messageData["name"]
612         elif messageData["prettyNumber"]:
613                 header = messageData["prettyNumber"]
614         else:
615                 header = "Unknown"
616         number = messageData["number"]
617         relativeTime = messageData["relTime"]
618
619         messageParts = list(messageData["messageParts"])
620         if len(messageParts) == 0:
621                 messages = ("No Transcription", )
622         elif len(messageParts) == 1:
623                 messages = (messageParts[0][1], )
624         else:
625                 messages = [
626                         "<b>%s</b>: %s" % (messagePart[0], messagePart[-1])
627                         for messagePart in messageParts
628                 ]
629
630         decoratedResults = header, number, relativeTime, messages
631         return decoratedResults
632
633
634 def test_backend(username, password):
635         backend = GVDialer()
636         print "Authenticated: ", backend.is_authed()
637         print "Login?: ", backend.login(username, password)
638         print "Authenticated: ", backend.is_authed()
639         # print "Token: ", backend._token
640         #print "Account: ", backend.get_account_number()
641         #print "Callback: ", backend.get_callback_number()
642         # print "All Callback: ",
643         #import pprint
644         # pprint.pprint(backend.get_callback_numbers())
645         # print "Recent: ",
646         # pprint.pprint(list(backend.get_recent()))
647         # print "Contacts: ",
648         # for contact in backend.get_contacts():
649         #       print contact
650         #       pprint.pprint(list(backend.get_contact_details(contact[0])))
651         #for message in backend.get_messages():
652         #  pprint.pprint(message)
653         #for message in sort_messages(backend.get_messages()):
654         #  pprint.pprint(decorate_message(message))
655
656         return backend
657
658
659 if __name__ == "__main__":
660         import sys
661         logging.basicConfig(level=logging.DEBUG)
662         test_backend(sys.argv[1], sys.argv[2])