Escaping messages so as to not be mistaken for HTML
[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         def __init__(self, cookieFile = None):
44                 self._gvoice = gvoice.GVoiceBackend(cookieFile)
45
46         def is_quick_login_possible(self):
47                 """
48                 @returns True then is_authed might be enough to login, else full login is required
49                 """
50                 return self._gvoice.is_quick_login_possible()
51
52         def is_authed(self, force = False):
53                 """
54                 Attempts to detect a current session
55                 @note Once logged in try not to reauth more than once a minute.
56                 @returns If authenticated
57                 """
58                 return self._gvoice.is_authed(force)
59
60         def login(self, username, password):
61                 """
62                 Attempt to login to GoogleVoice
63                 @returns Whether login was successful or not
64                 """
65                 return self._gvoice.login(username, password)
66
67         def logout(self):
68                 return self._gvoice.logout()
69
70         def persist(self):
71                 return self._gvoice.persist()
72
73         def is_dnd(self):
74                 return self._gvoice.is_dnd()
75
76         def set_dnd(self, doNotDisturb):
77                 return self._gvoice.set_dnd(doNotDisturb)
78
79         def call(self, outgoingNumber):
80                 """
81                 This is the main function responsible for initating the callback
82                 """
83                 return self._gvoice.call(outgoingNumber)
84
85         def cancel(self, outgoingNumber=None):
86                 """
87                 Cancels a call matching outgoing and forwarding numbers (if given). 
88                 Will raise an error if no matching call is being placed
89                 """
90                 return self._gvoice.cancel(outgoingNumber)
91
92         def send_sms(self, phoneNumbers, message):
93                 self._gvoice.send_sms(phoneNumbers, message)
94
95         def search(self, query):
96                 """
97                 Search your Google Voice Account history for calls, voicemails, and sms
98                 Returns ``Folder`` instance containting matching messages
99                 """
100                 return self._gvoice.search(query)
101
102         def get_feed(self, feed):
103                 return self._gvoice.get_feed(feed)
104
105         def download(self, messageId, adir):
106                 """
107                 Download a voicemail or recorded call MP3 matching the given ``msg``
108                 which can either be a ``Message`` instance, or a SHA1 identifier. 
109                 Saves files to ``adir`` (defaults to current directory). 
110                 Message hashes can be found in ``self.voicemail().messages`` for example. 
111                 Returns location of saved file.
112                 """
113                 return self._gvoice.download(messageId, adir)
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_contacts(self):
154                 """
155                 @returns Fresh dictionary of items
156                 """
157                 return dict(self._gvoice.get_contacts())
158
159         def get_messages(self):
160                 return list(self._get_messages())
161
162         def _get_messages(self):
163                 voicemails = self._gvoice.get_voicemails()
164                 smss = self._gvoice.get_texts()
165                 conversations = itertools.chain(voicemails, smss)
166                 for conversation in conversations:
167                         messages = conversation.messages
168                         messageParts = [
169                                 (message.whoFrom, self._format_message(message), message.when)
170                                 for message in messages
171                         ]
172
173                         messageDetails = {
174                                 "id": conversation.id,
175                                 "contactId": conversation.contactId,
176                                 "name": conversation.name,
177                                 "time": conversation.time,
178                                 "relTime": conversation.relTime,
179                                 "prettyNumber": conversation.prettyNumber,
180                                 "number": conversation.number,
181                                 "location": conversation.location,
182                                 "messageParts": messageParts,
183                                 "type": conversation.type,
184                                 "isRead": conversation.isRead,
185                                 "isTrash": conversation.isTrash,
186                                 "isSpam": conversation.isSpam,
187                                 "isArchived": conversation.isArchived,
188                         }
189                         yield messageDetails
190
191         def clear_caches(self):
192                 pass
193
194         def get_addressbooks(self):
195                 """
196                 @returns Iterable of (Address Book Factory, Book Id, Book Name)
197                 """
198                 yield self, "", ""
199
200         def open_addressbook(self, bookId):
201                 return self
202
203         @staticmethod
204         def contact_source_short_name(contactId):
205                 return "GV"
206
207         @staticmethod
208         def factory_name():
209                 return "Google Voice"
210
211         def _format_message(self, message):
212                 messagePartFormat = {
213                         "med1": "<i>%s</i>",
214                         "med2": "%s",
215                         "high": "<b>%s</b>",
216                 }
217                 return " ".join(
218                         messagePartFormat[text.accuracy] % io_utils.escape(text.text)
219                         for text in message.body
220                 )
221
222
223 def sort_messages(allMessages):
224         sortableAllMessages = [
225                 (message["time"], message)
226                 for message in allMessages
227         ]
228         sortableAllMessages.sort(reverse=True)
229         return (
230                 message
231                 for (exactTime, message) in sortableAllMessages
232         )
233
234
235 def decorate_recent(recentCallData):
236         """
237         @returns (personsName, phoneNumber, date, action)
238         """
239         contactId = recentCallData["contactId"]
240         if recentCallData["name"]:
241                 header = recentCallData["name"]
242         elif recentCallData["prettyNumber"]:
243                 header = recentCallData["prettyNumber"]
244         elif recentCallData["location"]:
245                 header = recentCallData["location"]
246         else:
247                 header = "Unknown"
248
249         number = recentCallData["number"]
250         relTime = recentCallData["relTime"]
251         action = recentCallData["action"]
252         return contactId, header, number, relTime, action
253
254
255 def decorate_message(messageData):
256         contactId = messageData["contactId"]
257         exactTime = messageData["time"]
258         if messageData["name"]:
259                 header = messageData["name"]
260         elif messageData["prettyNumber"]:
261                 header = messageData["prettyNumber"]
262         else:
263                 header = "Unknown"
264         number = messageData["number"]
265         relativeTime = messageData["relTime"]
266
267         messageParts = list(messageData["messageParts"])
268         if len(messageParts) == 0:
269                 messages = ("No Transcription", )
270         elif len(messageParts) == 1:
271                 messages = (messageParts[0][1], )
272         else:
273                 messages = [
274                         "<b>%s</b>: %s" % (messagePart[0], messagePart[1])
275                         for messagePart in messageParts
276                 ]
277
278         decoratedResults = contactId, header, number, relativeTime, messages
279         return decoratedResults