]>
code.delx.au - pymsnt/blob - src/tlib/msn/msnw.py
1 # Copyright 2004-2005 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
5 from twisted
.internet
import reactor
6 from twisted
.internet
.defer
import Deferred
7 from twisted
.internet
.protocol
import ClientFactory
10 import math
, base64
, binascii
13 from debug
import LogEvent
, INFO
, WARN
, ERROR
14 from tlib
.msn
import msn
19 All interaction should be with the MSNConnection and MultiSwitchboardSession classes.
20 You should not directly instantiate any objects of other classes.
24 """ Manages all the Twisted factories, etc """
26 SWITCHBOARDTIMEOUT
= 30.0*60.0
29 def __init__(self
, username
, password
, ident
):
30 """ Connects to the MSN servers.
31 @param username: the MSN passport to connect with.
32 @param password: the password for this account.
33 @param ident: a unique identifier to use in logging.
35 self
.username
= username
36 self
.password
= password
39 self
.notificationFactory
= None
40 self
.notificationClient
= None
42 LogEvent(INFO
, self
.ident
)
45 """ Automatically called by the constructor """
47 self
.switchboardSessions
= {}
48 self
.savedEvents
= SavedEvents() # Save any events that occur before connect
49 self
._getNotificationReferral
()
51 def _getNotificationReferral(self
):
55 self
.logOut() # Clean up everything
56 self
.timeout
= reactor
.callLater(30, timeout
)
57 dispatchFactory
= msn
.DispatchFactory()
58 dispatchFactory
.userHandle
= self
.username
59 dispatchFactory
.protocol
= DispatchClient
62 d
.addCallbacks(self
._gotNotificationReferral
, self
.connectionFailed
)
63 self
.connectors
.append(reactor
.connectTCP("messenger.hotmail.com", 1863, dispatchFactory
))
64 LogEvent(INFO
, self
.ident
)
66 def _gotNotificationReferral(self
, (host
, port
)):
68 # Create the NotificationClient
69 self
.notificationFactory
= msn
.NotificationFactory()
70 self
.notificationFactory
.userHandle
= self
.username
71 self
.notificationFactory
.password
= self
.password
72 self
.notificationFactory
.msncon
= self
73 self
.notificationFactory
.protocol
= NotificationClient
74 self
.connectors
.append(reactor
.connectTCP(host
, port
, self
.notificationFactory
))
75 LogEvent(INFO
, self
.ident
)
77 def _sendSavedEvents(self
):
78 self
.savedEvents
.send(self
)
79 self
.savedEvents
= None
81 def _notificationClientReady(self
, notificationClient
):
82 self
.notificationClient
= notificationClient
84 def _ensureSwitchboardSession(self
, userHandle
):
85 if not self
.switchboardSessions
.has_key(userHandle
):
86 sb
= OneSwitchboardSession(self
, userHandle
)
88 self
.switchboardSessions
[userHandle
] = sb
93 def getContacts(self
):
94 """ Gets the contact list.
96 @return an instance of MSNContactList (do not modify) if connected,
99 if self
.notificationFactory
:
100 return self
.notificationFactory
.contacts
104 def sendMessage(self
, userHandle
, text
, noerror
=False):
106 Sends a message to a contact. Can only be called after listSynchronized().
108 @param userHandle: the contact's MSN passport.
109 @param text: the text to send.
110 @param noerror: Set this to True if you don't want failed messages to bounce.
112 LogEvent(INFO
, self
.ident
)
113 if self
.notificationClient
:
114 self
._ensureSwitchboardSession
(userHandle
)
115 self
.switchboardSessions
[userHandle
].sendMessage(text
, noerror
)
117 self
.failedMessage(userHandle
, text
)
119 def sendAvatarRequest(self
, userHandle
):
121 Requests the avatar of a contact.
123 @param userHandle: the contact to request an avatar from.
124 @return: a Deferred() if the avatar can be fetched at this time.
125 This will fire with an argument of a tuple with the PNG
126 image data as the only element.
127 Otherwise returns None
130 LogEvent(INFO
, self
.ident
)
131 if not self
.notificationClient
: return
132 if MSNConnection
.GETALLAVATARS
:
133 self
._ensureSwitchboardSession
(userHandle
)
134 sb
= self
.switchboardSessions
.get(userHandle
)
135 if sb
: return sb
.sendAvatarRequest()
137 def sendFile(self
, userHandle
, filename
, filesize
):
139 Used to send a file to a contact.
141 @param username: the passport of the contact to send a file to.
142 @param filename: the name of the file to send.
143 @param filesize: the size of the file to send.
145 @return: A Deferred, which will fire with an argument of:
146 (fileSend, d) A FileSend object and a Deferred.
147 The new Deferred will pass one argument in a tuple,
148 whether or not the transfer is accepted. If you
149 receive a True, then you can call write() on the
150 fileSend object to send your file. Call close()
151 when the file is done.
152 NOTE: You MUST write() exactly as much as you
155 msnContact
= self
.getContacts().getContact(userHandle
)
157 raise ValueError, "Contact not found"
158 self
._ensureSwitchboardSession
(userHandle
)
159 return self
.switchboardSessions
[userHandle
].sendFile(msnContact
, filename
, filesize
)
161 def sendTypingToContact(self
, userHandle
):
163 Sends typing notification to a contact. Should send every 5secs.
164 @param userHandle: the contact to notify of our typing.
167 sb
= self
.switchboardSessions
.get(userHandle
)
168 if sb
: return sb
.sendTypingNotification()
170 def changeAvatar(self
, imageData
):
172 Changes the user's avatar.
173 @param imageData: the new PNG avatar image data.
175 if self
.notificationClient
:
176 LogEvent(INFO
, self
.ident
)
177 self
.notificationClient
.changeAvatar(imageData
, push
=True)
179 self
.savedEvents
.avatarImageData
= imageData
181 def changeStatus(self
, statusCode
, screenName
, personal
):
183 Changes your status details. All details must be given with
184 each call. This can be called before connection if you wish.
186 @param statusCode: the user's new status (look in msn.statusCodes).
187 @param screenName: the user's new screenName (up to 127 characters).
188 @param personal: the user's new personal message.
191 if not screenName
: screenName
= self
.username
192 if self
.notificationClient
:
193 changeCount
= [0] # Hack
194 def cb(ignored
=None):
196 if changeCount
[0] == 3:
197 self
.ourStatusChanged(statusCode
, screenName
, personal
)
198 LogEvent(INFO
, self
.ident
)
199 self
.notificationClient
.changeStatus(statusCode
.encode("utf-8")).addCallback(cb
)
200 self
.notificationClient
.changeScreenName(screenName
.encode("utf-8")).addCallback(cb
)
201 if not personal
: personal
= ""
202 self
.notificationClient
.changePersonalMessage(personal
.encode("utf-8")).addCallback(cb
)
204 self
.savedEvents
.statusCode
= statusCode
205 self
.savedEvents
.screenName
= screenName
206 self
.savedEvents
.personal
= personal
208 def addContact(self
, listType
, userHandle
):
209 """ See msn.NotificationClient.addContact """
210 if self
.notificationClient
:
211 return self
.notificationClient
.addContact(listType
, str(userHandle
))
213 self
.savedEvents
.addContacts
.append((listType
, str(userHandle
)))
215 def remContact(self
, listType
, userHandle
):
216 """ See msn.NotificationClient.remContact """
217 if self
.notificationClient
:
218 return self
.notificationClient
.remContact(listType
, str(userHandle
))
220 self
.savedEvents
.remContacts
.append((listType
, str(userHandle
)))
223 """ Shuts down the whole connection. Don't try to call any
224 other methods after this one. Except maybe connect() """
225 if self
.notificationClient
:
226 self
.notificationClient
.logOut()
227 for c
in self
.connectors
:
229 if self
.notificationFactory
:
230 self
.notificationFactory
.msncon
= None
232 for sbs
in self
.switchboardSessions
.values():
233 if hasattr(sbs
, "transport") and sbs
.transport
:
234 sbs
.transport
.loseConnection()
235 self
.switchboardSessions
= {}
236 LogEvent(INFO
, self
.ident
)
240 def connectionFailed(self
, reason
=''):
241 """ Called when the connection to the server failed. """
243 def loginFailed(self
, reason
=''):
244 """ Called when the account could not be logged in. """
246 def connectionLost(self
, reason
=''):
247 """ Called when we are disconnected. """
249 def multipleLogin(self
):
250 """ Called when the server says there has been another login
251 for this account. """
253 def serverGoingDown(self
):
254 """ Called when the server says that it will be going down. """
256 def accountNotVerified(self
):
257 """ Called if this passport has not been verified. Certain
258 functions are not available. """
260 def userMapping(self
, passport
, jid
):
261 """ Called when it is brought to our attention that one of the
262 MSN contacts has a Jabber ID. You should communicate with Jabber. """
265 """ Called when we have authenticated, but before we receive
266 the contact list. """
268 def listSynchronized(self
):
269 """ Called when we have received the contact list. All methods
270 in this class are now valid. """
272 def ourStatusChanged(self
, statusCode
, screenName
, personal
):
273 """ Called when the user's status has changed. """
275 def gotMessage(self
, userHandle
, text
):
276 """ Called when a contact sends us a message """
278 def gotGroupchat(self
, msnGroupchat
, userHandle
):
279 """ Called when a conversation with more than one contact begins.
280 userHandle is the person who invited us.
281 The overriding method is expected to set msnGroupchat.groupchat to an object
282 that implements the following methods:
283 contactJoined(userHandle)
284 contactLeft(userHandle)
285 gotMessage(userHandle, text)
287 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
290 def gotContactTyping(self
, userHandle
):
291 """ Called when a contact sends typing notification.
292 Will be called once every 5 seconds. """
294 def failedMessage(self
, userHandle
, text
):
295 """ Called when a message we sent has been bounced back. """
297 def contactAvatarChanged(self
, userHandle
, hash):
298 """ Called when we receive a changed avatar hash for a contact.
299 You should call sendAvatarRequest(). """
301 def contactStatusChanged(self
, userHandle
):
302 """ Called when we receive status information for a contact. """
304 def gotFileReceive(self
, fileReceive
):
305 """ Called when a contact sends the user a file.
306 Call accept(fileHandle) or reject() on the object. """
308 def contactAddedMe(self
, userHandle
):
309 """ Called when a contact adds the user to their list. """
311 def contactRemovedMe(self
, userHandle
):
312 """ Called when a contact removes the user from their list. """
314 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
315 """ Received at login to tell about the user's Hotmail status """
317 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
318 """ Received in realtime whenever an email comes into the hotmail account """
320 def gotMSNAlert(self
, body
, action
, subscr
):
321 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
322 text of the alert. 'action' is a url for more information,
323 'subscr' is a url to modify your your alerts subscriptions. """
325 def gotAvatarImageData(self
, userHandle
, imageData
):
326 """ An contact's avatar has been received because a switchboard
327 session with them was started. """
335 self
.avatarImageData
= ""
336 self
.addContacts
= []
337 self
.remContacts
= []
339 def send(self
, msncon
):
340 if self
.avatarImageData
:
341 msncon
.notificationClient
.changeAvatar(self
.avatarImageData
, push
=False)
342 if self
.screenName
or self
.statusCode
or self
.personal
:
343 msncon
.changeStatus(self
.statusCode
, self
.screenName
, self
.personal
)
344 for listType
, userHandle
in self
.addContacts
:
345 msncon
.addContact(listType
, userHandle
)
346 for listType
, userHandle
in self
.remContacts
:
347 msncon
.remContact(listType
, userHandle
)
351 class DispatchClient(msn
.DispatchClient
):
352 def gotNotificationReferral(self
, host
, port
):
353 if self
.factory
.d
.called
: return # Too slow! We've already timed out
354 self
.factory
.d
.callback((host
, port
))
357 class NotificationClient(msn
.NotificationClient
):
358 def loginFailure(self
, message
):
359 self
.factory
.msncon
.loginFailed(message
)
361 def loggedIn(self
, userHandle
, verified
):
362 LogEvent(INFO
, self
.factory
.msncon
.ident
)
363 msn
.NotificationClient
.loggedIn(self
, userHandle
, verified
)
364 self
.factory
.msncon
._notificationClientReady
(self
)
365 self
.factory
.msncon
.loggedIn()
367 self
.factory
.msncon
.accountNotVerified()
370 msn
.NotificationClient
.logOut(self
)
372 def connectionLost(self
, reason
):
373 if not self
.factory
.msncon
: return # If we called logOut
375 LogEvent(INFO
, self
.factory
.msncon
.ident
)
376 msn
.NotificationClient
.connectionLost(self
, reason
)
377 self
.factory
.msncon
.connectionLost(reason
)
378 # Make sure this event is handled after any others
379 reactor
.callLater(0, wait
)
381 def gotMSNAlert(self
, body
, action
, subscr
):
382 LogEvent(INFO
, self
.factory
.msncon
.ident
)
383 self
.factory
.msncon
.gotMSNAlert(body
, action
, subscr
)
385 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
386 LogEvent(INFO
, self
.factory
.msncon
.ident
)
387 self
.factory
.msncon
.gotInitialEmailNotification(inboxunread
, foldersunread
)
389 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
390 LogEvent(INFO
, self
.factory
.msncon
.ident
)
391 self
.factory
.msncon
.gotRealtimeEmailNotification(mailfrom
, fromaddr
, subject
)
393 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
394 LogEvent(INFO
, self
.factory
.msncon
.ident
)
395 self
.factory
.msncon
.contactAddedMe(userHandle
)
397 def userRemovedMe(self
, userHandle
):
398 LogEvent(INFO
, self
.factory
.msncon
.ident
)
399 self
.factory
.msncon
.contactRemovedMe(userHandle
)
401 def listSynchronized(self
, *args
):
402 LogEvent(INFO
, self
.factory
.msncon
.ident
)
403 self
.factory
.msncon
._sendSavedEvents
()
404 self
.factory
.msncon
.listSynchronized()
406 def contactAvatarChanged(self
, userHandle
, hash):
407 LogEvent(INFO
, self
.factory
.msncon
.ident
)
408 self
.factory
.msncon
.contactAvatarChanged(userHandle
, hash)
410 def gotContactStatus(self
, userHandle
, statusCode
, screenName
):
411 LogEvent(INFO
, self
.factory
.msncon
.ident
)
412 self
.factory
.msncon
.contactStatusChanged(userHandle
)
414 def contactStatusChanged(self
, userHandle
, statusCode
, screenName
):
415 LogEvent(INFO
, self
.factory
.msncon
.ident
)
416 self
.factory
.msncon
.contactStatusChanged(userHandle
)
418 def contactPersonalChanged(self
, userHandle
, personal
):
419 LogEvent(INFO
, self
.factory
.msncon
.ident
)
420 self
.factory
.msncon
.contactStatusChanged(userHandle
)
422 def contactOffline(self
, userHandle
):
423 LogEvent(INFO
, self
.factory
.msncon
.ident
)
424 self
.factory
.msncon
.contactStatusChanged(userHandle
)
426 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
427 LogEvent(INFO
, self
.factory
.msncon
.ident
)
428 sb
= self
.factory
.msncon
.switchboardSessions
.get(userHandle
)
429 if sb
and sb
.transport
:
430 sb
.transport
.loseConnection()
432 sb
= OneSwitchboardSession(self
.factory
.msncon
, userHandle
)
433 self
.factory
.msncon
.switchboardSessions
[userHandle
] = sb
434 sb
.connectReply(host
, port
, key
, sessionID
)
436 def multipleLogin(self
):
437 LogEvent(INFO
, self
.factory
.msncon
.ident
)
438 self
.factory
.msncon
.multipleLogin()
440 def serverGoingDown(self
):
441 LogEvent(INFO
, self
.factory
.msncon
.ident
)
442 self
.factory
.msncon
.serverGoingDown()
446 class SwitchboardSessionBase(msn
.SwitchboardClient
):
447 def __init__(self
, msncon
):
448 msn
.SwitchboardClient
.__init
__(self
)
450 self
.msnobj
= msncon
.notificationClient
.msnobj
451 self
.userHandle
= msncon
.username
452 self
.ident
= (msncon
.ident
, "INVALID!!")
453 self
.messageBuffer
= []
458 LogEvent(INFO
, self
.ident
)
460 self
.transport
.disconnect()
463 LogEvent(INFO
, self
.ident
)
468 LogEvent(INFO
, self
.ident
)
470 def sbRequestAccepted((host
, port
, key
)):
471 LogEvent(INFO
, self
.ident
)
474 factory
= ClientFactory()
475 factory
.buildProtocol
= lambda addr
: self
476 reactor
.connectTCP(host
, port
, factory
)
477 def sbRequestFailed(ignored
=None):
478 LogEvent(INFO
, self
.ident
)
479 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
480 d
= self
.msncon
.notificationClient
.requestSwitchboardServer()
481 d
.addCallbacks(sbRequestAccepted
, sbRequestFailed
)
483 def connectReply(self
, host
, port
, key
, sessionID
):
484 LogEvent(INFO
, self
.ident
)
487 self
.sessionID
= sessionID
489 factory
= ClientFactory()
490 factory
.buildProtocol
= lambda addr
: self
491 reactor
.connectTCP(host
, port
, factory
)
493 def flushBuffer(self
):
494 for message
, noerror
in self
.messageBuffer
[:]:
495 self
.messageBuffer
.remove((message
, noerror
))
496 self
.sendMessage(message
, noerror
)
497 for f
in self
.funcBuffer
[:]:
498 self
.funcBuffer
.remove(f
)
501 def failedMessage(self
, *ignored
):
502 raise NotImplementedError
504 def sendClientCaps(self
):
505 message
= msn
.MSNMessage()
506 message
.setHeader("Content-Type", "text/x-clientcaps")
507 message
.setHeader("Client-Name", "PyMSNt")
508 if hasattr(self
.msncon
, "jabberID"):
509 message
.setHeader("JabberID", str(self
.msncon
.jabberID
))
510 self
.sendMessage(message
)
512 def sendMessage(self
, message
, noerror
=False):
513 # Check to make sure that clientcaps only gets sent after
514 # the first text type message.
515 if isinstance(message
, msn
.MSNMessage
) and message
.getHeader("Content-Type").startswith("text"):
516 self
.sendMessage
= self
.sendMessageReal
517 self
.sendClientCaps()
518 return self
.sendMessage(message
, noerror
)
520 return self
.sendMessageReal(message
, noerror
)
522 def sendMessageReal(self
, text
, noerror
=False):
523 if not isinstance(text
, basestring
):
524 msn
.SwitchboardClient
.sendMessage(self
, text
)
527 self
.messageBuffer
.append((text
, noerror
))
529 LogEvent(INFO
, self
.ident
)
530 def failedMessage(ignored
):
532 self
.failedMessage(text
)
534 if len(text
) < MSNConnection
.MAXMESSAGESIZE
:
535 message
= msn
.MSNMessage(message
=str(text
.replace("\n", "\r\n").encode("utf-8")))
536 message
.setHeader("Content-Type", "text/plain; charset=UTF-8")
537 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
539 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
541 d
.addCallback(failedMessage
)
544 chunks
= int(math
.ceil(len(text
) / float(MSNConnection
.MAXMESSAGESIZE
)))
546 guid
= msn
.random_guid()
547 while chunk
< chunks
:
548 offset
= chunk
* MSNConnection
.MAXMESSAGESIZE
549 text
= message
[offset
: offset
+ MSNConnection
.MAXMESSAGESIZE
]
550 message
= msn
.MSNMessage(message
=str(text
.replace("\n", "\r\n").encode("utf-8")))
551 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
553 message
.setHeader("Content-Type", "text/plain; charset=UTF-8")
554 message
.setHeader("Chunks", str(chunks
))
556 message
.setHeader("Chunk", str(chunk
))
558 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
560 d
.addCallback(failedMessage
)
565 class MultiSwitchboardSession(SwitchboardSessionBase
):
566 """ Create one of me to chat to multiple contacts """
568 def __init__(self
, msncon
):
569 """ Automatically creates a new switchboard connection to the server """
570 SwitchboardSessionBase
.__init
__(self
, msncon
)
571 self
.ident
= (self
.msncon
.ident
, self
)
572 self
.contactCount
= 0
573 self
.groupchat
= None
576 def failedMessage(self
, text
):
577 self
.groupchat
.gotMessage("BOUNCE", text
)
579 def sendMessage(self
, text
, noerror
=False):
580 """ Used to send a mesage to the groupchat. Can be called immediately
581 after instantiation. """
582 if self
.contactCount
> 0:
583 SwitchboardSessionBase
.sendMessage(self
, text
, noerror
)
585 #self.messageBuffer.append((message, noerror))
586 pass # They're sending messages to an empty room. Ignore.
588 def inviteUser(self
, userHandle
):
589 """ Used to invite a contact to the groupchat. Can be called immediately
590 after instantiation. """
591 userHandle
= str(userHandle
)
593 LogEvent(INFO
, self
.ident
, "immediate")
594 msn
.SwitchboardClient
.inviteUser(self
, userHandle
)
596 LogEvent(INFO
, self
.ident
, "pending")
597 self
.funcBuffer
.append(lambda: msn
.SwitchboardClient
.inviteUser(self
, userHandle
))
599 def gotMessage(self
, message
):
600 self
.groupchat
.gotMessage(message
.userHandle
, message
.getMessage())
602 def userJoined(self
, userHandle
, screenName
=''):
603 LogEvent(INFO
, self
.ident
)
604 self
.contactCount
+= 1
605 self
.groupchat
.contactJoined(userHandle
)
607 def userLeft(self
, userHandle
):
608 LogEvent(INFO
, self
.ident
)
609 self
.contactCount
-= 1
610 self
.groupchat
.contactLeft(userHandle
)
614 class OneSwitchboardSession(SwitchboardSessionBase
):
615 def __init__(self
, msncon
, remoteUser
):
616 SwitchboardSessionBase
.__init
__(self
, msncon
)
617 self
.remoteUser
= str(remoteUser
)
618 self
.ident
= (self
.msncon
.ident
, self
.remoteUser
)
619 self
.chattingUsers
= []
624 self
.timeout
.cancel()
626 for message
, noerror
in self
.messageBuffer
:
628 self
.failedMessage(message
)
631 LogEvent(INFO
, self
.ident
)
633 for user
in self
.chattingUsers
:
634 self
.userJoined(user
)
636 self
.timeout
.cancel()
640 def _switchToMulti(self
, userHandle
):
641 LogEvent(INFO
, self
.ident
)
642 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
643 self
.__class
__ = MultiSwitchboardSession
645 self
.contactCount
= 0
646 self
.msncon
.gotGroupchat(self
, userHandle
)
647 if not self
.groupchat
:
648 LogEvent(ERROR
, self
.ident
)
649 raise Exception("YouNeedAGroupchat-WeHaveAProblemError") # FIXME
651 def failedMessage(self
, text
):
652 self
.msncon
.failedMessage(self
.remoteUser
, text
)
656 LogEvent(INFO
, self
.ident
)
658 def failCB(arg
=None):
659 if not (self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
)):
661 LogEvent(INFO
, self
.ident
, "User has not joined after 30 seconds.")
662 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
664 d
= self
.inviteUser(self
.remoteUser
)
666 self
.timeout
= reactor
.callLater(30.0, failCB
)
670 def gotChattingUsers(self
, users
):
671 for userHandle
in users
.keys():
672 self
.chattingUsers
.append(userHandle
)
674 def userJoined(self
, userHandle
, screenName
=''):
675 LogEvent(INFO
, self
.ident
)
678 if userHandle
!= self
.remoteUser
:
679 # Another user has joined, so we now have three participants.
680 remoteUser
= self
.remoteUser
681 self
._switchToMulti
(remoteUser
)
682 self
.userJoined(remoteUser
)
683 self
.userJoined(userHandle
)
685 def updateAvatarCB((imageData
, )):
687 self
.msncon
.gotAvatarImageData(self
.remoteUser
, imageData
)
688 d
= self
.sendAvatarRequest()
690 d
.addCallback(updateAvatarCB
)
692 def userLeft(self
, userHandle
):
694 if userHandle
== self
.remoteUser
:
695 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
696 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
697 reactor
.callLater(0, wait
) # Make sure this is handled after everything else
699 def gotMessage(self
, message
):
700 LogEvent(INFO
, self
.ident
)
701 cTypes
= [s
.strip() for s
in message
.getHeader("Content-Type").split(';')]
702 if "text/plain" == cTypes
[0]:
704 if len(cTypes
) > 1 and cTypes
[1].lower().find("utf-8") >= 0:
705 text
= message
.getMessage().decode("utf-8")
707 text
= message
.getMessage()
708 self
.msncon
.gotMessage(self
.remoteUser
, text
)
710 self
.msncon
.gotMessage(self
.remoteUser
, "A message was lost.")
712 elif "text/x-clientcaps" == cTypes
[0]:
713 if message
.hasHeader("JabberID"):
714 jid
= message
.getHeader("JabberID")
715 self
.msncon
.userMapping(message
.userHandle
, jid
)
717 LogEvent(INFO
, self
.ident
, "Discarding unknown message type.")
719 def gotFileReceive(self
, fileReceive
):
720 LogEvent(INFO
, self
.ident
)
721 self
.msncon
.gotFileReceive(fileReceive
)
723 def gotContactTyping(self
, message
):
724 LogEvent(INFO
, self
.ident
)
725 self
.msncon
.gotContactTyping(message
.userHandle
)
727 def sendTypingNotification(self
):
728 LogEvent(INFO
, self
.ident
)
730 msn
.SwitchboardClient
.sendTypingNotification(self
)
732 CAPS
= msn
.MSNContact
.MSNC1 | msn
.MSNContact
.MSNC2 | msn
.MSNContact
.MSNC3 | msn
.MSNContact
.MSNC4
733 def sendAvatarRequest(self
):
734 if not self
.ready
: return
735 msnContacts
= self
.msncon
.getContacts()
736 if not msnContacts
: return
737 msnContact
= msnContacts
.getContact(self
.remoteUser
)
738 if not (msnContact
and msnContact
.caps
& self
.CAPS
and msnContact
.msnobj
): return
739 if msnContact
.msnobjGot
: return
740 msnContact
.msnobjGot
= True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
741 return msn
.SwitchboardClient
.sendAvatarRequest(self
, msnContact
)
743 def sendFile(self
, msnContact
, filename
, filesize
):
744 def doSendFile(ignored
=None):
745 d
.callback(msn
.SwitchboardClient
.sendFile(self
, msnContact
, filename
, filesize
))
748 reactor
.callLater(0, doSendFile
)
750 self
.funcBuffer
.append(doSendFile
)