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