|
Package papyon ::
Module conversation
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Conversation
23
24 This module contains the classes needed to have a conversation with a
25 contact."""
26
27 import msnp
28 import p2p
29 from switchboard_manager import SwitchboardClient
30 from papyon.event import EventsDispatcher
31 from papyon.profile import NetworkID
32
33 import logging
34 import gobject
35 from urllib import quote, unquote
36
37 __all__ = ['Conversation', 'ConversationInterface', 'ConversationMessage', 'TextFormat']
38
39 logger = logging.getLogger('conversation')
43 """Factory function used to create the appropriate conversation with the
44 given contacts.
45
46 This is the method you need to use to start a conversation with both MSN
47 users and Yahoo! users.
48 @attention: you can only talk to one Yahoo! contact at a time, and you
49 cannot have multi-user conversations with both MSN and Yahoo! contacts.
50
51 @param contacts: The list of contacts to invite into the conversation
52 @type contacts: [L{Contact<papyon.profile.Contact>}, ...]
53
54 @returns: a Conversation object implementing L{ConversationInterface<papyon.conversation.ConversationInterface>}
55 @rtype: L{ConversationInterface<papyon.conversation.ConversationInterface>}
56 """
57 msn_contacts = set([contact for contact in contacts \
58 if contact.network_id == NetworkID.MSN])
59 external_contacts = set(contacts) - msn_contacts
60
61 if len(external_contacts) == 0:
62 return SwitchboardConversation(client, contacts)
63 elif len(msn_contacts) != 0:
64 raise NotImplementedError("The protocol doesn't allow mixing " \
65 "contacts from different networks in a single conversation")
66 elif len(external_contacts) > 1:
67 raise NotImplementedError("The protocol doesn't allow having " \
68 "more than one external contact in a conversation")
69 elif len(external_contacts) == 1:
70 return ExternalNetworkConversation(client, contacts)
71
74 """Interface implemented by all the Conversation objects, a Conversation
75 object allows the user to communicate with one or more peers"""
76
77 - def send_text_message(self, message):
78 """Send a message to all persons in this conversation.
79
80 @param message: the message to send to the users on this conversation
81 @type message: L{Contact<papyon.profile.Contact>}"""
82 raise NotImplementedError
83
85 """Sends a nudge to the contacts on this conversation."""
86 raise NotImplementedError
87
89 """Sends an user typing notification to the contacts on this
90 conversation."""
91 raise NotImplementedError
92
94 """Request a contact to join in the conversation.
95
96 @param contact: the contact to invite.
97 @type contact: L{Contact<papyon.profile.Contact>}"""
98 raise NotImplementedError
99
101 """Leave the conversation."""
102 raise NotImplementedError
103
106 """A Conversation message sent or received
107
108 @ivar display_name: the display name to show for the sender of this message
109 @type display_name: utf-8 encoded string
110
111 @ivar content: the content of the message
112 @type content: utf-8 encoded string
113
114 @ivar formatting: the formatting for this message
115 @type formatting: L{TextFormat<papyon.conversation.TextFormat>}
116
117 @ivar msn_objects: a dictionary mapping smileys
118 to an L{MSNObject<papyon.p2p.MSNObject>}
119 @type msn_objects: {smiley: string => L{MSNObject<papyon.p2p.MSNObject>}}
120 """
121 - def __init__(self, content, formatting=None, msn_objects={}):
122 """Initializer
123
124 @param content: the content of the message
125 @type content: utf-8 encoded string
126
127 @param formatting: the formatting for this message
128 @type formatting: L{TextFormat<papyon.conversation.TextFormat>}
129
130 @param msn_objects: a dictionary mapping smileys
131 to an L{MSNObject<papyon.p2p.MSNObject>}
132 @type msn_objects: {smiley: string => L{MSNObject<papyon.p2p.MSNObject>}}"""
133 self.display_name = None
134 self.content = content
135 self.formatting = formatting
136 self.msn_objects = msn_objects
137
281
290
291 - def send_text_message(self, message):
292 if len(message.msn_objects) > 0:
293 body = []
294 for alias, msn_object in message.msn_objects.iteritems():
295 self._client._msn_object_store.publish(msn_object)
296 body.append(alias.encode("utf-8"))
297 body.append(str(msn_object))
298
299
300 self._send_message(("text/x-mms-animemoticon",), '\t'.join(body))
301
302 content_type = ("text/plain","utf-8")
303 body = message.content.encode("utf-8")
304 ack = msnp.MessageAcknowledgement.HALF
305 headers = {}
306 if message.formatting is not None:
307 headers["X-MMS-IM-Format"] = str(message.formatting)
308
309 self._send_message(content_type, body, headers, ack)
310
312 content_type = "text/x-msnmsgr-datacast"
313 body = "ID: 1\r\n\r\n".encode('UTF-8')
314 ack = msnp.MessageAcknowledgement.NONE
315 self._send_message(content_type, body, ack=ack)
316
318 content_type = "text/x-msmsgscontrol"
319 body = "\r\n\r\n".encode('UTF-8')
320 headers = { "TypingUser" : self._client.profile.account.encode('UTF_8') }
321 ack = msnp.MessageAcknowledgement.NONE
322 self._send_message(content_type, body, headers, ack)
323
325 raise NotImplementedError
326
328 raise NotImplementedError
329
330 - def _send_message(self, content_type, body, headers={},
331 ack=msnp.MessageAcknowledgement.HALF):
332 raise NotImplementedError
333
336
339
341 sender = message.sender
342 message_type = message.content_type[0]
343 message_encoding = message.content_type[1]
344 try:
345 message_formatting = message.get_header('X-MMS-IM-Format')
346 if not message_formatting:
347 message_formatting = '='
348 except KeyError:
349 message_formatting = '='
350
351 if message_type == 'text/plain':
352 msg = ConversationMessage(unicode(message.body, message_encoding),
353 TextFormat.parse(message_formatting),
354 self.__last_received_msn_objects)
355 try:
356 display_name = message.get_header('P4-Context')
357 except KeyError:
358 display_name = sender.display_name
359 msg.display_name = display_name
360 self._dispatch("on_conversation_message_received", sender, msg)
361 self.__last_received_msn_objects = {}
362 elif message_type == 'text/x-msmsgscontrol':
363 self._dispatch("on_conversation_user_typing", sender)
364 elif message_type in ['text/x-mms-emoticon',
365 'text/x-mms-animemoticon']:
366 msn_objects = {}
367 parts = message.body.split('\t')
368 logger.debug(parts)
369 for i in [i for i in range(len(parts)) if not i % 2]:
370 if parts[i] == '': break
371 msn_objects[parts[i]] = p2p.MSNObject.parse(self._client,
372 parts[i+1])
373 self.__last_received_msn_objects = msn_objects
374 elif message_type == 'text/x-msnmsgr-datacast' and \
375 message.body.strip() == "ID: 1":
376 self._dispatch("on_conversation_nudge_received", sender)
377
380
382 self._dispatch("on_conversation_error", error_type, error)
383
387 AbstractConversation.__init__(self, client)
388 self.participants = set(contacts)
389 client._register_external_conversation(self)
390 gobject.idle_add(self._open)
391
393 for contact in self.participants:
394 self._on_contact_joined(contact)
395 return False
396
398 raise NotImplementedError("The protocol doesn't allow multiuser " \
399 "conversations for external contacts")
400
402 self._client._unregister_external_conversation(self)
403
404 - def _send_message(self, content_type, body, headers={},
405 ack=msnp.MessageAcknowledgement.HALF):
406 if content_type[0] in ['text/x-mms-emoticon',
407 'text/x-mms-animemoticon']:
408 return
409 message = msnp.Message(self._client.profile)
410 for key, value in headers.iteritems():
411 message.add_header(key, value)
412 message.content_type = content_type
413 message.body = body
414 for contact in self.participants:
415 self._client._protocol.\
416 send_unmanaged_message(contact, message)
417
423
424 @staticmethod
426 content_type = message.content_type[0]
427 if switchboard_client is None:
428 return content_type in ('text/plain', 'text/x-msnmsgr-datacast')
429
430 return content_type in ('text/plain', 'text/x-msmsgscontrol',
431 'text/x-msnmsgr-datacast', 'text/x-mms-emoticon',
432 'text/x-mms-animemoticon')
433
435 """Request a contact to join in the conversation.
436
437 @param contact: the contact to invite.
438 @type contact: L{profile.Contact}"""
439 SwitchboardClient._invite_user(self, contact)
440
442 """Leave the conversation."""
443 SwitchboardClient._leave(self)
444
445 - def _send_message(self, content_type, body, headers={},
446 ack=msnp.MessageAcknowledgement.HALF):
447 SwitchboardClient._send_message(self, content_type, body, headers, ack)
448