]>
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
):
56 self
.logOut() # Clean up everything
57 self
.timeout
= reactor
.callLater(30, timeout
)
58 dispatchFactory
= msn
.DispatchFactory()
59 dispatchFactory
.userHandle
= self
.username
60 dispatchFactory
.protocol
= DispatchClient
63 d
.addCallbacks(self
._gotNotificationReferral
, self
.connectionFailed
)
64 self
.connectors
.append(reactor
.connectTCP("messenger.hotmail.com", 1863, dispatchFactory
))
65 LogEvent(INFO
, self
.ident
)
67 def _gotNotificationReferral(self
, (host
, port
)):
70 # Create the NotificationClient
71 self
.notificationFactory
= msn
.NotificationFactory()
72 self
.notificationFactory
.userHandle
= self
.username
73 self
.notificationFactory
.password
= self
.password
74 self
.notificationFactory
.msncon
= self
75 self
.notificationFactory
.protocol
= NotificationClient
76 self
.connectors
.append(reactor
.connectTCP(host
, port
, self
.notificationFactory
))
77 LogEvent(INFO
, self
.ident
)
79 def _sendSavedEvents(self
):
80 self
.savedEvents
.send(self
)
81 self
.savedEvents
= None
83 def _notificationClientReady(self
, notificationClient
):
84 self
.notificationClient
= notificationClient
86 def _ensureSwitchboardSession(self
, userHandle
):
87 if not self
.switchboardSessions
.has_key(userHandle
):
88 sb
= OneSwitchboardSession(self
, userHandle
)
90 self
.switchboardSessions
[userHandle
] = sb
95 def getContacts(self
):
96 """ Gets the contact list.
98 @return an instance of MSNContactList (do not modify) if connected,
101 if self
.notificationFactory
:
102 return self
.notificationFactory
.contacts
106 def sendMessage(self
, userHandle
, text
, noerror
=False):
108 Sends a message to a contact. Can only be called after listSynchronized().
110 @param userHandle: the contact's MSN passport.
111 @param text: the text to send.
112 @param noerror: Set this to True if you don't want failed messages to bounce.
114 LogEvent(INFO
, self
.ident
)
115 if self
.notificationClient
:
116 self
._ensureSwitchboardSession
(userHandle
)
117 self
.switchboardSessions
[userHandle
].sendMessage(text
, noerror
)
119 self
.failedMessage(userHandle
, text
)
121 def sendAvatarRequest(self
, userHandle
):
123 Requests the avatar of a contact.
125 @param userHandle: the contact to request an avatar from.
126 @return: a Deferred() if the avatar can be fetched at this time.
127 This will fire with an argument of a tuple with the PNG
128 image data as the only element.
129 Otherwise returns None
132 LogEvent(INFO
, self
.ident
)
133 if not self
.notificationClient
: return
134 if MSNConnection
.GETALLAVATARS
:
135 self
._ensureSwitchboardSession
(userHandle
)
136 sb
= self
.switchboardSessions
.get(userHandle
)
137 if sb
: return sb
.sendAvatarRequest()
139 def sendFile(self
, userHandle
, filename
, filesize
):
141 Used to send a file to a contact.
143 @param username: the passport of the contact to send a file to.
144 @param filename: the name of the file to send.
145 @param filesize: the size of the file to send.
147 @return: A Deferred, which will fire with an argument of:
148 (fileSend, d) A FileSend object and a Deferred.
149 The new Deferred will pass one argument in a tuple,
150 whether or not the transfer is accepted. If you
151 receive a True, then you can call write() on the
152 fileSend object to send your file. Call close()
153 when the file is done.
154 NOTE: You MUST write() exactly as much as you
157 msnContact
= self
.getContacts().getContact(userHandle
)
159 raise ValueError, "Contact not found"
160 self
._ensureSwitchboardSession
(userHandle
)
161 return self
.switchboardSessions
[userHandle
].sendFile(msnContact
, filename
, filesize
)
163 def sendTypingToContact(self
, userHandle
):
165 Sends typing notification to a contact. Should send every 5secs.
166 @param userHandle: the contact to notify of our typing.
169 sb
= self
.switchboardSessions
.get(userHandle
)
170 if sb
: return sb
.sendTypingNotification()
172 def changeAvatar(self
, imageData
):
174 Changes the user's avatar.
175 @param imageData: the new PNG avatar image data.
177 if self
.notificationClient
:
178 LogEvent(INFO
, self
.ident
)
179 self
.notificationClient
.changeAvatar(imageData
, push
=True)
181 self
.savedEvents
.avatarImageData
= imageData
183 def changeStatus(self
, statusCode
, screenName
, personal
):
185 Changes your status details. All details must be given with
186 each call. This can be called before connection if you wish.
188 @param statusCode: the user's new status (look in msn.statusCodes).
189 @param screenName: the user's new screenName (up to 127 characters).
190 @param personal: the user's new personal message.
193 if not screenName
: screenName
= self
.username
194 if not statusCode
: statusCode
= msn
.STATUS_ONLINE
195 if not personal
: personal
= ""
196 if self
.notificationClient
:
197 changeCount
= [0] # Hack
198 def cb(ignored
=None):
200 if changeCount
[0] == 3:
201 self
.ourStatusChanged(statusCode
, screenName
, personal
)
202 LogEvent(INFO
, self
.ident
)
203 self
.notificationClient
.changeStatus(statusCode
.encode("utf-8")).addCallback(cb
)
204 self
.notificationClient
.changeScreenName(screenName
.encode("utf-8")).addCallback(cb
)
205 self
.notificationClient
.changePersonalMessage(personal
.encode("utf-8")).addCallback(cb
)
207 self
.savedEvents
.statusCode
= statusCode
208 self
.savedEvents
.screenName
= screenName
209 self
.savedEvents
.personal
= personal
211 def addContact(self
, listType
, userHandle
):
212 """ See msn.NotificationClient.addContact """
213 if self
.notificationClient
:
214 return self
.notificationClient
.addContact(listType
, str(userHandle
))
216 self
.savedEvents
.addContacts
.append((listType
, str(userHandle
)))
218 def remContact(self
, listType
, userHandle
):
219 """ See msn.NotificationClient.remContact """
220 if self
.notificationClient
:
221 return self
.notificationClient
.remContact(listType
, str(userHandle
))
223 self
.savedEvents
.remContacts
.append((listType
, str(userHandle
)))
226 """ Shuts down the whole connection. Don't try to call any
227 other methods after this one. Except maybe connect() """
228 if self
.notificationClient
:
229 self
.notificationClient
.logOut()
230 for c
in self
.connectors
:
232 if self
.notificationFactory
:
233 self
.notificationFactory
.msncon
= None
235 for sbs
in self
.switchboardSessions
.values():
236 if hasattr(sbs
, "transport") and sbs
.transport
:
237 sbs
.transport
.loseConnection()
238 self
.switchboardSessions
= {}
240 self
.timeout
.cancel()
242 LogEvent(INFO
, self
.ident
)
246 def connectionFailed(self
, reason
=''):
247 """ Called when the connection to the server failed. """
249 def loginFailed(self
, reason
=''):
250 """ Called when the account could not be logged in. """
252 def connectionLost(self
, reason
=''):
253 """ Called when we are disconnected. """
255 def multipleLogin(self
):
256 """ Called when the server says there has been another login
257 for this account. """
259 def serverGoingDown(self
):
260 """ Called when the server says that it will be going down. """
262 def accountNotVerified(self
):
263 """ Called if this passport has not been verified. Certain
264 functions are not available. """
266 def userMapping(self
, passport
, jid
):
267 """ Called when it is brought to our attention that one of the
268 MSN contacts has a Jabber ID. You should communicate with Jabber. """
271 """ Called when we have authenticated, but before we receive
272 the contact list. """
274 def listSynchronized(self
):
275 """ Called when we have received the contact list. All methods
276 in this class are now valid. """
278 def ourStatusChanged(self
, statusCode
, screenName
, personal
):
279 """ Called when the user's status has changed. """
281 def gotMessage(self
, userHandle
, text
):
282 """ Called when a contact sends us a message """
284 def gotGroupchat(self
, msnGroupchat
, userHandle
):
285 """ Called when a conversation with more than one contact begins.
286 userHandle is the person who invited us.
287 The overriding method is expected to set msnGroupchat.groupchat to an object
288 that implements the following methods:
289 contactJoined(userHandle)
290 contactLeft(userHandle)
291 gotMessage(userHandle, text)
293 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
296 def gotContactTyping(self
, userHandle
):
297 """ Called when a contact sends typing notification.
298 Will be called once every 5 seconds. """
300 def failedMessage(self
, userHandle
, text
):
301 """ Called when a message we sent has been bounced back. """
303 def contactAvatarChanged(self
, userHandle
, hash):
304 """ Called when we receive a changed avatar hash for a contact.
305 You should call sendAvatarRequest(). """
307 def contactStatusChanged(self
, userHandle
):
308 """ Called when we receive status information for a contact. """
310 def gotFileReceive(self
, fileReceive
):
311 """ Called when a contact sends the user a file.
312 Call accept(fileHandle) or reject() on the object. """
314 def contactAddedMe(self
, userHandle
):
315 """ Called when a contact adds the user to their list. """
317 def contactRemovedMe(self
, userHandle
):
318 """ Called when a contact removes the user from their list. """
320 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
321 """ Received at login to tell about the user's Hotmail status """
323 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
324 """ Received in realtime whenever an email comes into the hotmail account """
326 def gotMSNAlert(self
, body
, action
, subscr
):
327 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
328 text of the alert. 'action' is a url for more information,
329 'subscr' is a url to modify your your alerts subscriptions. """
331 def gotAvatarImageData(self
, userHandle
, imageData
):
332 """ An contact's avatar has been received because a switchboard
333 session with them was started. """
341 self
.avatarImageData
= ""
342 self
.addContacts
= []
343 self
.remContacts
= []
345 def send(self
, msncon
):
346 if self
.avatarImageData
:
347 msncon
.notificationClient
.changeAvatar(self
.avatarImageData
, push
=False)
348 if self
.screenName
or self
.statusCode
or self
.personal
:
349 msncon
.changeStatus(self
.statusCode
, self
.screenName
, self
.personal
)
350 for listType
, userHandle
in self
.addContacts
:
351 msncon
.addContact(listType
, userHandle
)
352 for listType
, userHandle
in self
.remContacts
:
353 msncon
.remContact(listType
, userHandle
)
357 class DispatchClient(msn
.DispatchClient
):
358 def gotNotificationReferral(self
, host
, port
):
359 if self
.factory
.d
.called
: return # Too slow! We've already timed out
360 self
.factory
.d
.callback((host
, port
))
363 class NotificationClient(msn
.NotificationClient
):
364 def loginFailure(self
, message
):
365 self
.factory
.msncon
.loginFailed(message
)
367 def loggedIn(self
, userHandle
, verified
):
368 LogEvent(INFO
, self
.factory
.msncon
.ident
)
369 msn
.NotificationClient
.loggedIn(self
, userHandle
, verified
)
370 self
.factory
.msncon
._notificationClientReady
(self
)
371 self
.factory
.msncon
.loggedIn()
373 self
.factory
.msncon
.accountNotVerified()
376 msn
.NotificationClient
.logOut(self
)
378 def connectionLost(self
, reason
):
379 if not self
.factory
.msncon
: return # If we called logOut
381 LogEvent(INFO
, self
.factory
.msncon
.ident
)
382 msn
.NotificationClient
.connectionLost(self
, reason
)
383 self
.factory
.msncon
.connectionLost(reason
)
384 # Make sure this event is handled after any others
385 reactor
.callLater(0, wait
)
387 def gotMSNAlert(self
, body
, action
, subscr
):
388 LogEvent(INFO
, self
.factory
.msncon
.ident
)
389 self
.factory
.msncon
.gotMSNAlert(body
, action
, subscr
)
391 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
392 LogEvent(INFO
, self
.factory
.msncon
.ident
)
393 self
.factory
.msncon
.gotInitialEmailNotification(inboxunread
, foldersunread
)
395 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
396 LogEvent(INFO
, self
.factory
.msncon
.ident
)
397 self
.factory
.msncon
.gotRealtimeEmailNotification(mailfrom
, fromaddr
, subject
)
399 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
400 LogEvent(INFO
, self
.factory
.msncon
.ident
)
401 self
.factory
.msncon
.contactAddedMe(userHandle
)
403 def userRemovedMe(self
, userHandle
):
404 LogEvent(INFO
, self
.factory
.msncon
.ident
)
405 self
.factory
.msncon
.contactRemovedMe(userHandle
)
407 def listSynchronized(self
, *args
):
408 LogEvent(INFO
, self
.factory
.msncon
.ident
)
409 self
.factory
.msncon
._sendSavedEvents
()
410 self
.factory
.msncon
.listSynchronized()
412 def contactAvatarChanged(self
, userHandle
, hash):
413 LogEvent(INFO
, self
.factory
.msncon
.ident
)
414 self
.factory
.msncon
.contactAvatarChanged(userHandle
, hash)
416 def gotContactStatus(self
, userHandle
, statusCode
, screenName
):
417 LogEvent(INFO
, self
.factory
.msncon
.ident
)
418 self
.factory
.msncon
.contactStatusChanged(userHandle
)
420 def contactStatusChanged(self
, userHandle
, statusCode
, screenName
):
421 LogEvent(INFO
, self
.factory
.msncon
.ident
)
422 self
.factory
.msncon
.contactStatusChanged(userHandle
)
424 def contactPersonalChanged(self
, userHandle
, personal
):
425 LogEvent(INFO
, self
.factory
.msncon
.ident
)
426 self
.factory
.msncon
.contactStatusChanged(userHandle
)
428 def contactOffline(self
, userHandle
):
429 LogEvent(INFO
, self
.factory
.msncon
.ident
)
430 self
.factory
.msncon
.contactStatusChanged(userHandle
)
432 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
433 LogEvent(INFO
, self
.factory
.msncon
.ident
)
434 sb
= self
.factory
.msncon
.switchboardSessions
.get(userHandle
)
435 if sb
and sb
.transport
:
436 sb
.transport
.loseConnection()
438 sb
= OneSwitchboardSession(self
.factory
.msncon
, userHandle
)
439 self
.factory
.msncon
.switchboardSessions
[userHandle
] = sb
440 sb
.connectReply(host
, port
, key
, sessionID
)
442 def multipleLogin(self
):
443 LogEvent(INFO
, self
.factory
.msncon
.ident
)
444 self
.factory
.msncon
.multipleLogin()
446 def serverGoingDown(self
):
447 LogEvent(INFO
, self
.factory
.msncon
.ident
)
448 self
.factory
.msncon
.serverGoingDown()
452 class SwitchboardSessionBase(msn
.SwitchboardClient
):
453 def __init__(self
, msncon
):
454 msn
.SwitchboardClient
.__init
__(self
)
456 self
.msnobj
= msncon
.notificationClient
.msnobj
457 self
.userHandle
= msncon
.username
458 self
.ident
= (msncon
.ident
, "INVALID!!")
459 self
.messageBuffer
= []
464 LogEvent(INFO
, self
.ident
)
466 self
.transport
.disconnect()
469 LogEvent(INFO
, self
.ident
)
474 LogEvent(INFO
, self
.ident
)
476 def sbRequestAccepted((host
, port
, key
)):
477 LogEvent(INFO
, self
.ident
)
480 factory
= ClientFactory()
481 factory
.buildProtocol
= lambda addr
: self
482 reactor
.connectTCP(host
, port
, factory
)
483 def sbRequestFailed(ignored
=None):
484 LogEvent(INFO
, self
.ident
)
485 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
486 d
= self
.msncon
.notificationClient
.requestSwitchboardServer()
487 d
.addCallbacks(sbRequestAccepted
, sbRequestFailed
)
489 def connectReply(self
, host
, port
, key
, sessionID
):
490 LogEvent(INFO
, self
.ident
)
493 self
.sessionID
= sessionID
495 factory
= ClientFactory()
496 factory
.buildProtocol
= lambda addr
: self
497 reactor
.connectTCP(host
, port
, factory
)
499 def flushBuffer(self
):
500 for message
, noerror
in self
.messageBuffer
[:]:
501 self
.messageBuffer
.remove((message
, noerror
))
502 self
.sendMessage(message
, noerror
)
503 for f
in self
.funcBuffer
[:]:
504 self
.funcBuffer
.remove(f
)
507 def failedMessage(self
, *ignored
):
508 raise NotImplementedError
510 def sendClientCaps(self
):
511 message
= msn
.MSNMessage()
512 message
.setHeader("Content-Type", "text/x-clientcaps")
513 message
.setHeader("Client-Name", "PyMSNt")
514 if hasattr(self
.msncon
, "jabberID"):
515 message
.setHeader("JabberID", str(self
.msncon
.jabberID
))
516 self
.sendMessage(message
)
518 def sendMessage(self
, message
, noerror
=False):
519 # Check to make sure that clientcaps only gets sent after
520 # the first text type message.
521 if isinstance(message
, msn
.MSNMessage
) and message
.getHeader("Content-Type").startswith("text"):
522 self
.sendMessage
= self
.sendMessageReal
523 self
.sendClientCaps()
524 return self
.sendMessage(message
, noerror
)
526 return self
.sendMessageReal(message
, noerror
)
528 def sendMessageReal(self
, text
, noerror
=False):
529 if not isinstance(text
, basestring
):
530 msn
.SwitchboardClient
.sendMessage(self
, text
)
533 self
.messageBuffer
.append((text
, noerror
))
535 LogEvent(INFO
, self
.ident
)
536 text
= str(text
.replace("\n", "\r\n").encode("utf-8"))
537 def failedMessage(ignored
):
539 self
.failedMessage(text
)
541 if len(text
) < MSNConnection
.MAXMESSAGESIZE
:
542 message
= msn
.MSNMessage(message
=text
)
543 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
545 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
547 d
.addCallback(failedMessage
)
550 chunks
= int(math
.ceil(len(text
) / float(MSNConnection
.MAXMESSAGESIZE
)))
552 guid
= msn
.random_guid()
553 while chunk
< chunks
:
554 offset
= chunk
* MSNConnection
.MAXMESSAGESIZE
555 message
= msn
.MSNMessage(message
=text
[offset
: offset
+ MSNConnection
.MAXMESSAGESIZE
])
556 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
557 message
.setHeader("Message-ID", guid
)
559 message
.setHeader("Chunks", str(chunks
))
561 message
.delHeader("MIME-Version")
562 message
.delHeader("Content-Type")
563 message
.setHeader("Chunk", str(chunk
))
565 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
567 d
.addCallback(failedMessage
)
572 class MultiSwitchboardSession(SwitchboardSessionBase
):
573 """ Create one of me to chat to multiple contacts """
575 def __init__(self
, msncon
):
576 """ Automatically creates a new switchboard connection to the server """
577 SwitchboardSessionBase
.__init
__(self
, msncon
)
578 self
.ident
= (self
.msncon
.ident
, self
)
579 self
.contactCount
= 0
580 self
.groupchat
= None
583 def failedMessage(self
, text
):
584 self
.groupchat
.gotMessage("BOUNCE", text
)
586 def sendMessage(self
, text
, noerror
=False):
587 """ Used to send a mesage to the groupchat. Can be called immediately
588 after instantiation. """
589 if self
.contactCount
> 0:
590 SwitchboardSessionBase
.sendMessage(self
, text
, noerror
)
592 #self.messageBuffer.append((message, noerror))
593 pass # They're sending messages to an empty room. Ignore.
595 def inviteUser(self
, userHandle
):
596 """ Used to invite a contact to the groupchat. Can be called immediately
597 after instantiation. """
598 userHandle
= str(userHandle
)
600 LogEvent(INFO
, self
.ident
, "immediate")
601 msn
.SwitchboardClient
.inviteUser(self
, userHandle
)
603 LogEvent(INFO
, self
.ident
, "pending")
604 self
.funcBuffer
.append(lambda: msn
.SwitchboardClient
.inviteUser(self
, userHandle
))
606 def gotMessage(self
, message
):
607 self
.groupchat
.gotMessage(message
.userHandle
, message
.getMessage())
609 def userJoined(self
, userHandle
, screenName
=''):
610 LogEvent(INFO
, self
.ident
)
611 self
.contactCount
+= 1
612 self
.groupchat
.contactJoined(userHandle
)
614 def userLeft(self
, userHandle
):
615 LogEvent(INFO
, self
.ident
)
616 self
.contactCount
-= 1
617 self
.groupchat
.contactLeft(userHandle
)
621 class OneSwitchboardSession(SwitchboardSessionBase
):
622 def __init__(self
, msncon
, remoteUser
):
623 SwitchboardSessionBase
.__init
__(self
, msncon
)
624 self
.remoteUser
= str(remoteUser
)
625 self
.ident
= (self
.msncon
.ident
, self
.remoteUser
)
626 self
.chattingUsers
= []
631 self
.timeout
.cancel()
633 for message
, noerror
in self
.messageBuffer
:
635 self
.failedMessage(message
)
638 LogEvent(INFO
, self
.ident
)
640 for user
in self
.chattingUsers
:
641 self
.userJoined(user
)
643 self
.timeout
.cancel()
647 def _switchToMulti(self
, userHandle
):
648 LogEvent(INFO
, self
.ident
)
649 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
650 self
.__class
__ = MultiSwitchboardSession
652 self
.contactCount
= 0
653 self
.msncon
.gotGroupchat(self
, userHandle
)
654 assert self
.groupchat
656 def failedMessage(self
, text
):
657 self
.msncon
.failedMessage(self
.remoteUser
, text
)
661 LogEvent(INFO
, self
.ident
)
663 def failCB(arg
=None):
664 if not (self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
)):
666 LogEvent(INFO
, self
.ident
, "User has not joined after 30 seconds.")
667 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
669 d
= self
.inviteUser(self
.remoteUser
)
671 self
.timeout
= reactor
.callLater(30.0, failCB
)
675 def gotChattingUsers(self
, users
):
676 for userHandle
in users
.keys():
677 self
.chattingUsers
.append(userHandle
)
679 def userJoined(self
, userHandle
, screenName
=''):
680 LogEvent(INFO
, self
.ident
)
683 if userHandle
!= self
.remoteUser
:
684 # Another user has joined, so we now have three participants.
685 remoteUser
= self
.remoteUser
686 self
._switchToMulti
(remoteUser
)
687 self
.userJoined(remoteUser
)
688 self
.userJoined(userHandle
)
690 def updateAvatarCB((imageData
, )):
692 self
.msncon
.gotAvatarImageData(self
.remoteUser
, imageData
)
693 d
= self
.sendAvatarRequest()
695 d
.addCallback(updateAvatarCB
)
697 def userLeft(self
, userHandle
):
699 if userHandle
== self
.remoteUser
:
700 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
701 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
702 reactor
.callLater(0, wait
) # Make sure this is handled after everything else
704 def gotMessage(self
, message
):
705 LogEvent(INFO
, self
.ident
)
706 cTypes
= [s
.strip() for s
in message
.getHeader("Content-Type").split(';')]
707 if "text/plain" == cTypes
[0]:
709 if len(cTypes
) > 1 and cTypes
[1].lower().find("utf-8") >= 0:
710 text
= message
.getMessage().decode("utf-8")
712 text
= message
.getMessage()
713 self
.msncon
.gotMessage(self
.remoteUser
, text
)
715 self
.msncon
.gotMessage(self
.remoteUser
, "A message was lost.")
717 elif "text/x-clientcaps" == cTypes
[0]:
718 if message
.hasHeader("JabberID"):
719 jid
= message
.getHeader("JabberID")
720 self
.msncon
.userMapping(message
.userHandle
, jid
)
722 LogEvent(INFO
, self
.ident
, "Discarding unknown message type.")
724 def gotFileReceive(self
, fileReceive
):
725 LogEvent(INFO
, self
.ident
)
726 self
.msncon
.gotFileReceive(fileReceive
)
728 def gotContactTyping(self
, message
):
729 LogEvent(INFO
, self
.ident
)
730 self
.msncon
.gotContactTyping(message
.userHandle
)
732 def sendTypingNotification(self
):
733 LogEvent(INFO
, self
.ident
)
735 msn
.SwitchboardClient
.sendTypingNotification(self
)
737 CAPS
= msn
.MSNContact
.MSNC1 | msn
.MSNContact
.MSNC2 | msn
.MSNContact
.MSNC3 | msn
.MSNContact
.MSNC4
738 def sendAvatarRequest(self
):
739 if not self
.ready
: return
740 msnContacts
= self
.msncon
.getContacts()
741 if not msnContacts
: return
742 msnContact
= msnContacts
.getContact(self
.remoteUser
)
743 if not (msnContact
and msnContact
.caps
& self
.CAPS
and msnContact
.msnobj
): return
744 if msnContact
.msnobjGot
: return
745 msnContact
.msnobjGot
= True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
746 return msn
.SwitchboardClient
.sendAvatarRequest(self
, msnContact
)
748 def sendFile(self
, msnContact
, filename
, filesize
):
749 def doSendFile(ignored
=None):
750 d
.callback(msn
.SwitchboardClient
.sendFile(self
, msnContact
, filename
, filesize
))
753 reactor
.callLater(0, doSendFile
)
755 self
.funcBuffer
.append(doSendFile
)