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