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