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