24140498365fed784b292f84c64dbd8cf5b2901b
[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, adir):
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                 Saves files to ``adir`` (defaults to current directory). 
111                 Message hashes can be found in ``self.voicemail().messages`` for example. 
112                 Returns location of saved file.
113                 """
114                 return self._gvoice.download(messageId, adir)
115
116         def is_valid_syntax(self, number):
117                 """
118                 @returns If This number be called ( syntax validation only )
119                 """
120                 return self._gvoice.is_valid_syntax(number)
121
122         def get_account_number(self):
123                 """
124                 @returns The GoogleVoice phone number
125                 """
126                 return self._gvoice.get_account_number()
127
128         def get_callback_numbers(self):
129                 """
130                 @returns a dictionary mapping call back numbers to descriptions
131                 @note These results are cached for 30 minutes.
132                 """
133                 return self._gvoice.get_callback_numbers()
134
135         def set_callback_number(self, callbacknumber):
136                 """
137                 Set the number that GoogleVoice calls
138                 @param callbacknumber should be a proper 10 digit number
139                 """
140                 return self._gvoice.set_callback_number(callbacknumber)
141
142         def get_callback_number(self):
143                 """
144                 @returns Current callback number or None
145                 """
146                 return self._gvoice.get_callback_number()
147
148         def get_recent(self):
149                 """
150                 @returns Iterable of (personsName, phoneNumber, exact date, relative date, action)
151                 """
152                 return list(self._gvoice.get_recent())
153
154         def get_messages(self, messageType):
155                 messages = list(self._get_messages(messageType))
156                 messages.sort(key=lambda message: message["time"])
157                 return messages
158
159         def _get_messages(self, messageType):
160                 if messageType in [self.MESSAGE_VOICEMAILS, self.MESSAGE_ALL] or not self._voicemails:
161                         self._voicemails = list(self._gvoice.get_voicemails())
162                 voicemails = self._voicemails
163                 if messageType in [self.MESSAGE_TEXTS, self.MESSAGE_ALL] or not self._texts:
164                         self._texts = list(self._gvoice.get_texts())
165                 smss = self._texts
166
167                 conversations = itertools.chain(voicemails, smss)
168                 for conversation in conversations:
169                         messages = conversation.messages
170                         messageParts = [
171                                 (message.whoFrom, self._format_message(message), message.when)
172                                 for message in messages
173                         ]
174
175                         messageDetails = {
176                                 "id": conversation.id,
177                                 "contactId": conversation.contactId,
178                                 "name": conversation.name,
179                                 "time": conversation.time,
180                                 "relTime": conversation.relTime,
181                                 "prettyNumber": conversation.prettyNumber,
182                                 "number": conversation.number,
183                                 "location": conversation.location,
184                                 "messageParts": messageParts,
185                                 "type": conversation.type,
186                                 "isRead": conversation.isRead,
187                                 "isTrash": conversation.isTrash,
188                                 "isSpam": conversation.isSpam,
189                                 "isArchived": conversation.isArchived,
190                         }
191                         yield messageDetails
192
193         def clear_caches(self):
194                 pass
195
196         def get_addressbooks(self):
197                 """
198                 @returns Iterable of (Address Book Factory, Book Id, Book Name)
199                 """
200                 yield self, "", ""
201
202         def open_addressbook(self, bookId):
203                 return self
204
205         @staticmethod
206         def contact_source_short_name(contactId):
207                 return "GV"
208
209         @staticmethod
210         def factory_name():
211                 return "Google Voice"
212
213         def _format_message(self, message):
214                 messagePartFormat = {
215                         "med1": "<i>%s</i>",
216                         "med2": "%s",
217                         "high": "<b>%s</b>",
218                 }
219                 return " ".join(
220                         messagePartFormat[text.accuracy] % io_utils.escape(text.text)
221                         for text in message.body
222                 )
223
224
225 def sort_messages(allMessages):
226         sortableAllMessages = [
227                 (message["time"], message)
228                 for message in allMessages
229         ]
230         sortableAllMessages.sort(reverse=True)
231         return (
232                 message
233                 for (exactTime, message) in sortableAllMessages
234         )
235
236
237 def decorate_recent(recentCallData):
238         """
239         @returns (personsName, phoneNumber, date, action)
240         """
241         contactId = recentCallData["contactId"]
242         if recentCallData["name"]:
243                 header = recentCallData["name"]
244         elif recentCallData["prettyNumber"]:
245                 header = recentCallData["prettyNumber"]
246         elif recentCallData["location"]:
247                 header = recentCallData["location"]
248         else:
249                 header = "Unknown"
250
251         number = recentCallData["number"]
252         relTime = recentCallData["relTime"]
253         action = recentCallData["action"]
254         return contactId, header, number, relTime, action
255
256
257 def decorate_message(messageData):
258         contactId = messageData["contactId"]
259         exactTime = messageData["time"]
260         if messageData["name"]:
261                 header = messageData["name"]
262         elif messageData["prettyNumber"]:
263                 header = messageData["prettyNumber"]
264         else:
265                 header = "Unknown"
266         number = messageData["number"]
267         relativeTime = messageData["relTime"]
268
269         messageParts = list(messageData["messageParts"])
270         if len(messageParts) == 0:
271                 messages = ("No Transcription", )
272         elif len(messageParts) == 1:
273                 messages = (messageParts[0][1], )
274         else:
275                 messages = [
276                         "<b>%s</b>: %s" % (messagePart[0], messagePart[1])
277                         for messagePart in messageParts
278                 ]
279
280         decoratedResults = contactId, header, number, relativeTime, messages
281         return decoratedResults