Creating a hollow shell of a UI
[gc-dialer] / src / backends / gv_backend.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 from __future__ import with_statement
29
30 import itertools
31 import logging
32
33 from gvoice import gvoice
34
35
36 _moduleLogger = logging.getLogger(__name__)
37
38
39 class GVDialer(object):
40
41         def __init__(self, cookieFile = None):
42                 self._gvoice = gvoice.GVoiceBackend(cookieFile)
43
44                 self._contacts = None
45
46         def is_quick_login_possible(self):
47                 """
48                 @returns True then is_authed might be enough to login, else full login is required
49                 """
50                 return self._gvoice.is_quick_login_possible()
51
52         def is_authed(self, force = False):
53                 """
54                 Attempts to detect a current session
55                 @note Once logged in try not to reauth more than once a minute.
56                 @returns If authenticated
57                 """
58                 return self._gvoice.is_authed(force)
59
60         def login(self, username, password):
61                 """
62                 Attempt to login to GoogleVoice
63                 @returns Whether login was successful or not
64                 """
65                 return self._gvoice.login(username, password)
66
67         def logout(self):
68                 return self._gvoice.logout()
69
70         def is_dnd(self):
71                 return self._gvoice.is_dnd()
72
73         def set_dnd(self, doNotDisturb):
74                 return self._gvoice.set_dnd(doNotDisturb)
75
76         def call(self, outgoingNumber):
77                 """
78                 This is the main function responsible for initating the callback
79                 """
80                 return self._gvoice.call(outgoingNumber)
81
82         def cancel(self, outgoingNumber=None):
83                 """
84                 Cancels a call matching outgoing and forwarding numbers (if given). 
85                 Will raise an error if no matching call is being placed
86                 """
87                 return self._gvoice.cancel(outgoingNumber)
88
89         def send_sms(self, phoneNumbers, message):
90                 self._gvoice.send_sms(phoneNumbers, message)
91
92         def search(self, query):
93                 """
94                 Search your Google Voice Account history for calls, voicemails, and sms
95                 Returns ``Folder`` instance containting matching messages
96                 """
97                 return self._gvoice.search(query)
98
99         def get_feed(self, feed):
100                 return self._gvoice.get_feed(feed)
101
102         def download(self, messageId, adir):
103                 """
104                 Download a voicemail or recorded call MP3 matching the given ``msg``
105                 which can either be a ``Message`` instance, or a SHA1 identifier. 
106                 Saves files to ``adir`` (defaults to current directory). 
107                 Message hashes can be found in ``self.voicemail().messages`` for example. 
108                 Returns location of saved file.
109                 """
110                 return self._gvoice.download(messageId, adir)
111
112         def is_valid_syntax(self, number):
113                 """
114                 @returns If This number be called ( syntax validation only )
115                 """
116                 return self._gvoice.is_valid_syntax(number)
117
118         def get_account_number(self):
119                 """
120                 @returns The GoogleVoice phone number
121                 """
122                 return self._gvoice.get_account_number()
123
124         def get_callback_numbers(self):
125                 """
126                 @returns a dictionary mapping call back numbers to descriptions
127                 @note These results are cached for 30 minutes.
128                 """
129                 return self._gvoice.get_callback_numbers()
130
131         def set_callback_number(self, callbacknumber):
132                 """
133                 Set the number that GoogleVoice calls
134                 @param callbacknumber should be a proper 10 digit number
135                 """
136                 return self._gvoice.set_callback_number(callbacknumber)
137
138         def get_callback_number(self):
139                 """
140                 @returns Current callback number or None
141                 """
142                 return self._gvoice.get_callback_number()
143
144         def get_recent(self):
145                 """
146                 @returns Iterable of (personsName, phoneNumber, exact date, relative date, action)
147                 """
148                 return self._gvoice.get_recent()
149
150         def get_contacts(self):
151                 """
152                 @returns Iterable of (contact id, contact name)
153                 """
154                 self._update_contacts_cache()
155                 contactsToSort = [
156                         (contactDetails["name"], contactId)
157                         for contactId, contactDetails in self._contacts.iteritems()
158                 ]
159                 contactsToSort.sort()
160                 return (
161                         (contactId, contactName)
162                         for (contactName, contactId) in contactsToSort
163                 )
164
165         def get_contact_details(self, contactId):
166                 """
167                 @returns Iterable of (Phone Type, Phone Number)
168                 """
169                 if self._contacts is None:
170                         self._update_contacts_cache()
171                 contactDetails = self._contacts[contactId]
172                 # Defaulting phoneTypes because those are just things like faxes
173                 return (
174                         (number.get("phoneType", ""), number["phoneNumber"])
175                         for number in contactDetails["numbers"]
176                 )
177
178         def get_messages(self):
179                 voicemails = self._gvoice.get_voicemails()
180                 smss = self._gvoice.get_texts()
181                 conversations = itertools.chain(voicemails, smss)
182                 for conversation in conversations:
183                         messages = conversation.messages
184                         messageParts = (
185                                 (message.whoFrom, self._format_message(message), message.when)
186                                 for message in messages
187                         )
188
189                         messageDetails = {
190                                 "id": conversation.id,
191                                 "contactId": conversation.contactId,
192                                 "name": conversation.name,
193                                 "time": conversation.time,
194                                 "relTime": conversation.relTime,
195                                 "prettyNumber": conversation.prettyNumber,
196                                 "number": conversation.number,
197                                 "location": conversation.location,
198                                 "messageParts": messageParts,
199                                 "type": conversation.type,
200                                 "isRead": conversation.isRead,
201                                 "isTrash": conversation.isTrash,
202                                 "isSpam": conversation.isSpam,
203                                 "isArchived": conversation.isArchived,
204                         }
205                         yield messageDetails
206
207         def clear_caches(self):
208                 pass
209
210         def get_addressbooks(self):
211                 """
212                 @returns Iterable of (Address Book Factory, Book Id, Book Name)
213                 """
214                 yield self, "", ""
215
216         def open_addressbook(self, bookId):
217                 return self
218
219         @staticmethod
220         def contact_source_short_name(contactId):
221                 return "GV"
222
223         @staticmethod
224         def factory_name():
225                 return "Google Voice"
226
227         def _update_contacts_cache(self):
228                 self._contacts = dict(self._gvoice.get_contacts())
229
230         def _format_message(self, message):
231                 messagePartFormat = {
232                         "med1": "<i>%s</i>",
233                         "med2": "%s",
234                         "high": "<b>%s</b>",
235                 }
236                 return " ".join(
237                         messagePartFormat[text.accuracy] % text.text
238                         for text in message.body
239                 )
240
241
242 def sort_messages(allMessages):
243         sortableAllMessages = [
244                 (message["time"], message)
245                 for message in allMessages
246         ]
247         sortableAllMessages.sort(reverse=True)
248         return (
249                 message
250                 for (exactTime, message) in sortableAllMessages
251         )
252
253
254 def decorate_recent(recentCallData):
255         """
256         @returns (personsName, phoneNumber, date, action)
257         """
258         contactId = recentCallData["contactId"]
259         if recentCallData["name"]:
260                 header = recentCallData["name"]
261         elif recentCallData["prettyNumber"]:
262                 header = recentCallData["prettyNumber"]
263         elif recentCallData["location"]:
264                 header = recentCallData["location"]
265         else:
266                 header = "Unknown"
267
268         number = recentCallData["number"]
269         relTime = recentCallData["relTime"]
270         action = recentCallData["action"]
271         return contactId, header, number, relTime, action
272
273
274 def decorate_message(messageData):
275         contactId = messageData["contactId"]
276         exactTime = messageData["time"]
277         if messageData["name"]:
278                 header = messageData["name"]
279         elif messageData["prettyNumber"]:
280                 header = messageData["prettyNumber"]
281         else:
282                 header = "Unknown"
283         number = messageData["number"]
284         relativeTime = messageData["relTime"]
285
286         messageParts = list(messageData["messageParts"])
287         if len(messageParts) == 0:
288                 messages = ("No Transcription", )
289         elif len(messageParts) == 1:
290                 messages = (messageParts[0][1], )
291         else:
292                 messages = [
293                         "<b>%s</b>: %s" % (messagePart[0], messagePart[1])
294                         for messagePart in messageParts
295                 ]
296
297         decoratedResults = contactId, header, number, relativeTime, messages
298         return decoratedResults