A case was made for keeping in the unknown phone types
[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                 # Defaulting phoneTypes because those are just things like faxes
172                 return (
173                         (number.get("phoneType", ""), number["phoneNumber"])
174                         for number in contactDetails["numbers"]
175                 )
176
177         def get_messages(self):
178                 conversations = self._gvoice.get_conversations()
179                 for conversation in conversations:
180                         messages = conversation.messages
181                         messageParts = (
182                                 (message.whoFrom, self._format_message(message), message.when)
183                                 for message in messages
184                         )
185
186                         messageDetails = {
187                                 "id": conversation.id,
188                                 "contactId": conversation.contactId,
189                                 "name": conversation.name,
190                                 "time": conversation.time,
191                                 "relTime": conversation.relTime,
192                                 "prettyNumber": conversation.prettyNumber,
193                                 "number": conversation.number,
194                                 "location": conversation.location,
195                                 "messageParts": messageParts,
196                                 "type": conversation.type,
197                                 "isRead": conversation.isRead,
198                                 "isTrash": conversation.isTrash,
199                                 "isSpam": conversation.isSpam,
200                                 "isArchived": conversation.isArchived,
201                         }
202                         yield messageDetails
203
204         def clear_caches(self):
205                 pass
206
207         def get_addressbooks(self):
208                 """
209                 @returns Iterable of (Address Book Factory, Book Id, Book Name)
210                 """
211                 yield self, "", ""
212
213         def open_addressbook(self, bookId):
214                 return self
215
216         @staticmethod
217         def contact_source_short_name(contactId):
218                 return "GV"
219
220         @staticmethod
221         def factory_name():
222                 return "Google Voice"
223
224         def _update_contacts_cache(self):
225                 self._contacts = dict(self._gvoice.get_contacts())
226
227         def _format_message(self, message):
228                 messagePartFormat = {
229                         "med1": "<i>%s</i>",
230                         "med2": "%s",
231                         "high": "<b>%s</b>",
232                 }
233                 return " ".join(
234                         messagePartFormat[text.accuracy] % text.text
235                         for text in message.body
236                 )
237
238
239 def sort_messages(allMessages):
240         sortableAllMessages = [
241                 (message["time"], message)
242                 for message in allMessages
243         ]
244         sortableAllMessages.sort(reverse=True)
245         return (
246                 message
247                 for (exactTime, message) in sortableAllMessages
248         )
249
250
251 def decorate_recent(recentCallData):
252         """
253         @returns (personsName, phoneNumber, date, action)
254         """
255         contactId = recentCallData["contactId"]
256         if recentCallData["name"]:
257                 header = recentCallData["name"]
258         elif recentCallData["prettyNumber"]:
259                 header = recentCallData["prettyNumber"]
260         elif recentCallData["location"]:
261                 header = recentCallData["location"]
262         else:
263                 header = "Unknown"
264
265         number = recentCallData["number"]
266         relTime = recentCallData["relTime"]
267         action = recentCallData["action"]
268         return contactId, header, number, relTime, action
269
270
271 def decorate_message(messageData):
272         contactId = messageData["contactId"]
273         exactTime = messageData["time"]
274         if messageData["name"]:
275                 header = messageData["name"]
276         elif messageData["prettyNumber"]:
277                 header = messageData["prettyNumber"]
278         else:
279                 header = "Unknown"
280         number = messageData["number"]
281         relativeTime = messageData["relTime"]
282
283         messageParts = list(messageData["messageParts"])
284         if len(messageParts) == 0:
285                 messages = ("No Transcription", )
286         elif len(messageParts) == 1:
287                 messages = (messageParts[0][1], )
288         else:
289                 messages = [
290                         "<b>%s</b>: %s" % (messagePart[0], messagePart[1])
291                         for messagePart in messageParts
292                 ]
293
294         decoratedResults = contactId, header, number, relativeTime, messages
295         return decoratedResults