]>
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 d
.errback(Exception("Timeout"))
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
)
82 def _notificationClientReady(self
, notificationClient
):
83 self
.notificationClient
= notificationClient
85 def _ensureSwitchboardSession(self
, userHandle
):
86 if not self
.switchboardSessions
.has_key(userHandle
):
87 sb
= OneSwitchboardSession(self
, userHandle
)
89 self
.switchboardSessions
[userHandle
] = sb
94 def getContacts(self
):
95 """ Gets the contact list.
97 @return an instance of MSNContactList (do not modify) if connected,
100 if self
.notificationFactory
:
101 return self
.notificationFactory
.contacts
105 def sendMessage(self
, userHandle
, text
, noerror
=False):
107 Sends a message to a contact. Can only be called after listSynchronized().
109 @param userHandle: the contact's MSN passport.
110 @param text: the text to send.
111 @param noerror: Set this to True if you don't want failed messages to bounce.
113 LogEvent(INFO
, self
.ident
)
114 if self
.notificationClient
:
115 self
._ensureSwitchboardSession
(userHandle
)
116 self
.switchboardSessions
[userHandle
].sendMessage(text
, noerror
)
118 self
.failedMessage(userHandle
, text
)
120 def sendAvatarRequest(self
, userHandle
):
122 Requests the avatar of a contact.
124 @param userHandle: the contact to request an avatar from.
125 @return: a Deferred() if the avatar can be fetched at this time.
126 This will fire with an argument of a tuple with the PNG
127 image data as the only element.
128 Otherwise returns None
131 LogEvent(INFO
, self
.ident
)
132 if not self
.notificationClient
: return
133 if MSNConnection
.GETALLAVATARS
:
134 self
._ensureSwitchboardSession
(userHandle
)
135 sb
= self
.switchboardSessions
.get(userHandle
)
136 if sb
: return sb
.sendAvatarRequest()
138 def sendFile(self
, userHandle
, filename
, filesize
):
140 Used to send a file to a contact.
142 @param username: the passport of the contact to send a file to.
143 @param filename: the name of the file to send.
144 @param filesize: the size of the file to send.
146 @return: A Deferred, which will fire with an argument of:
147 (fileSend, d) A FileSend object and a Deferred.
148 The new Deferred will pass one argument in a tuple,
149 whether or not the transfer is accepted. If you
150 receive a True, then you can call write() on the
151 fileSend object to send your file. Call close()
152 when the file is done.
153 NOTE: You MUST write() exactly as much as you
156 msnContact
= self
.getContacts().getContact(userHandle
)
158 raise ValueError, "Contact not found"
159 self
._ensureSwitchboardSession
(userHandle
)
160 return self
.switchboardSessions
[userHandle
].sendFile(msnContact
, filename
, filesize
)
162 def sendTypingToContact(self
, userHandle
):
164 Sends typing notification to a contact. Should send every 5secs.
165 @param userHandle: the contact to notify of our typing.
168 sb
= self
.switchboardSessions
.get(userHandle
)
169 if sb
: return sb
.sendTypingNotification()
171 def changeAvatar(self
, imageData
):
173 Changes the user's avatar.
174 @param imageData: the new PNG avatar image data.
176 if self
.notificationClient
:
177 LogEvent(INFO
, self
.ident
)
178 self
.notificationClient
.changeAvatar(imageData
, push
=True)
179 # Save the avatar for reuse on disconnection
180 self
.savedEvents
.avatarImageData
= imageData
182 def changeStatus(self
, statusCode
, screenName
, personal
):
184 Changes your status details. All details must be given with
185 each call. This can be called before connection if you wish.
187 @param statusCode: the user's new status (look in msn.statusCodes).
188 @param screenName: the user's new screenName (up to 127 characters).
189 @param personal: the user's new personal message.
192 if not screenName
: screenName
= self
.username
193 if not statusCode
: statusCode
= msn
.STATUS_ONLINE
194 if not personal
: personal
= ""
195 if self
.notificationClient
:
196 changeCount
= [0] # Hack
197 def cb(ignored
=None):
199 if changeCount
[0] == 3:
200 self
.ourStatusChanged(statusCode
, screenName
, personal
)
201 LogEvent(INFO
, self
.ident
)
202 self
.notificationClient
.changeStatus(statusCode
.encode("utf-8")).addCallback(cb
)
203 self
.notificationClient
.changeScreenName(screenName
.encode("utf-8")).addCallback(cb
)
204 self
.notificationClient
.changePersonalMessage(personal
.encode("utf-8")).addCallback(cb
)
205 # Remember the saved status
206 self
.savedEvents
.statusCode
= statusCode
207 self
.savedEvents
.screenName
= screenName
208 self
.savedEvents
.personal
= personal
210 def addContact(self
, listType
, userHandle
):
211 """ See msn.NotificationClient.addContact """
212 if self
.notificationClient
:
213 return self
.notificationClient
.addContact(listType
, str(userHandle
))
215 self
.savedEvents
.addContacts
.append((listType
, str(userHandle
)))
217 def remContact(self
, listType
, userHandle
):
218 """ See msn.NotificationClient.remContact """
219 if self
.notificationClient
:
220 return self
.notificationClient
.remContact(listType
, str(userHandle
))
222 self
.savedEvents
.remContacts
.append((listType
, str(userHandle
)))
225 """ Shuts down the whole connection. Don't try to call any
226 other methods after this one. Except maybe connect() """
227 if self
.notificationClient
:
228 self
.notificationClient
.logOut()
229 for c
in self
.connectors
:
231 if self
.notificationFactory
:
232 self
.notificationFactory
.msncon
= None
234 for sbs
in self
.switchboardSessions
.values():
235 if hasattr(sbs
, "transport") and sbs
.transport
:
236 sbs
.transport
.loseConnection()
237 self
.switchboardSessions
= {}
239 self
.timeout
.cancel()
241 LogEvent(INFO
, self
.ident
)
245 def connectionFailed(self
, reason
=''):
246 """ Called when the connection to the server failed. """
248 def loginFailed(self
, reason
=''):
249 """ Called when the account could not be logged in. """
251 def connectionLost(self
, reason
=''):
252 """ Called when we are disconnected. """
254 def multipleLogin(self
):
255 """ Called when the server says there has been another login
256 for this account. """
258 def serverGoingDown(self
):
259 """ Called when the server says that it will be going down. """
261 def accountNotVerified(self
):
262 """ Called if this passport has not been verified. Certain
263 functions are not available. """
265 def userMapping(self
, passport
, jid
):
266 """ Called when it is brought to our attention that one of the
267 MSN contacts has a Jabber ID. You should communicate with Jabber. """
270 """ Called when we have authenticated, but before we receive
271 the contact list. """
273 def listSynchronized(self
):
274 """ Called when we have received the contact list. All methods
275 in this class are now valid. """
277 def ourStatusChanged(self
, statusCode
, screenName
, personal
):
278 """ Called when the user's status has changed. """
280 def gotMessage(self
, userHandle
, text
):
281 """ Called when a contact sends us a message """
283 def gotGroupchat(self
, msnGroupchat
, userHandle
):
284 """ Called when a conversation with more than one contact begins.
285 userHandle is the person who invited us.
286 The overriding method is expected to set msnGroupchat.groupchat to an object
287 that implements the following methods:
288 contactJoined(userHandle)
289 contactLeft(userHandle)
290 gotMessage(userHandle, text)
292 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
295 def gotContactTyping(self
, userHandle
):
296 """ Called when a contact sends typing notification.
297 Will be called once every 5 seconds. """
299 def failedMessage(self
, userHandle
, text
):
300 """ Called when a message we sent has been bounced back. """
302 def contactAvatarChanged(self
, userHandle
, hash):
303 """ Called when we receive a changed avatar hash for a contact.
304 You should call sendAvatarRequest(). """
306 def contactStatusChanged(self
, userHandle
):
307 """ Called when we receive status information for a contact. """
309 def gotFileReceive(self
, fileReceive
):
310 """ Called when a contact sends the user a file.
311 Call accept(fileHandle) or reject() on the object. """
313 def contactAddedMe(self
, userHandle
):
314 """ Called when a contact adds the user to their list. """
316 def contactRemovedMe(self
, userHandle
):
317 """ Called when a contact removes the user from their list. """
319 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
320 """ Received at login to tell about the user's Hotmail status """
322 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
323 """ Received in realtime whenever an email comes into the hotmail account """
325 def gotMSNAlert(self
, body
, action
, subscr
):
326 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
327 text of the alert. 'action' is a url for more information,
328 'subscr' is a url to modify your your alerts subscriptions. """
330 def gotAvatarImageData(self
, userHandle
, imageData
):
331 """ An contact's avatar has been received because a switchboard
332 session with them was started. """
340 self
.avatarImageData
= ""
341 self
.addContacts
= []
342 self
.remContacts
= []
344 def send(self
, msncon
):
345 if self
.avatarImageData
:
346 msncon
.notificationClient
.changeAvatar(self
.avatarImageData
, push
=False)
347 if self
.screenName
or self
.statusCode
or self
.personal
:
348 msncon
.changeStatus(self
.statusCode
, self
.screenName
, self
.personal
)
349 for listType
, userHandle
in self
.addContacts
:
350 msncon
.addContact(listType
, userHandle
)
351 for listType
, userHandle
in self
.remContacts
:
352 msncon
.remContact(listType
, userHandle
)
356 class DispatchClient(msn
.DispatchClient
):
357 def gotNotificationReferral(self
, host
, port
):
358 if self
.factory
.d
.called
: return # Too slow! We've already timed out
359 self
.factory
.d
.callback((host
, port
))
362 class NotificationClient(msn
.NotificationClient
):
363 def doDisconnect(self
, *args
):
364 if hasattr(self
, "transport") and self
.transport
:
365 self
.transport
.loseConnection()
367 def loginFailure(self
, message
):
368 self
.factory
.msncon
.loginFailed(message
)
370 def loggedIn(self
, userHandle
, verified
):
371 LogEvent(INFO
, self
.factory
.msncon
.ident
)
372 msn
.NotificationClient
.loggedIn(self
, userHandle
, verified
)
373 self
.factory
.msncon
._notificationClientReady
(self
)
374 self
.factory
.msncon
.loggedIn()
376 self
.factory
.msncon
.accountNotVerified()
379 msn
.NotificationClient
.logOut(self
)
380 # If we explicitly log out, then all of these events
382 self
.loginFailure
= self
.doDisconnect
383 self
.loggedIn
= self
.doDisconnect
384 self
.connectionLost
= self
.doDisconnect
386 def connectionLost(self
, reason
):
388 LogEvent(INFO
, self
.factory
.msncon
.ident
)
389 msn
.NotificationClient
.connectionLost(self
, reason
)
390 if self
.factory
.maxRetries
> self
.factory
.retries
:
391 self
.factory
.stopTrying()
392 self
.factory
.msncon
.connectionLost(reason
)
393 # Make sure this event is handled after any others
394 reactor
.callLater(0, wait
)
396 def gotMSNAlert(self
, body
, action
, subscr
):
397 LogEvent(INFO
, self
.factory
.msncon
.ident
)
398 self
.factory
.msncon
.gotMSNAlert(body
, action
, subscr
)
400 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
401 LogEvent(INFO
, self
.factory
.msncon
.ident
)
402 self
.factory
.msncon
.gotInitialEmailNotification(inboxunread
, foldersunread
)
404 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
405 LogEvent(INFO
, self
.factory
.msncon
.ident
)
406 self
.factory
.msncon
.gotRealtimeEmailNotification(mailfrom
, fromaddr
, subject
)
408 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
409 LogEvent(INFO
, self
.factory
.msncon
.ident
)
410 self
.factory
.msncon
.contactAddedMe(userHandle
)
412 def userRemovedMe(self
, userHandle
):
413 LogEvent(INFO
, self
.factory
.msncon
.ident
)
414 self
.factory
.msncon
.contactRemovedMe(userHandle
)
416 def listSynchronized(self
, *args
):
417 LogEvent(INFO
, self
.factory
.msncon
.ident
)
418 self
.factory
.msncon
._sendSavedEvents
()
419 self
.factory
.msncon
.listSynchronized()
421 def contactAvatarChanged(self
, userHandle
, hash):
422 LogEvent(INFO
, self
.factory
.msncon
.ident
)
423 self
.factory
.msncon
.contactAvatarChanged(userHandle
, hash)
425 def gotContactStatus(self
, userHandle
, statusCode
, screenName
):
426 LogEvent(INFO
, self
.factory
.msncon
.ident
)
427 self
.factory
.msncon
.contactStatusChanged(userHandle
)
429 def contactStatusChanged(self
, userHandle
, statusCode
, screenName
):
430 LogEvent(INFO
, self
.factory
.msncon
.ident
)
431 self
.factory
.msncon
.contactStatusChanged(userHandle
)
433 def contactPersonalChanged(self
, userHandle
, personal
):
434 LogEvent(INFO
, self
.factory
.msncon
.ident
)
435 self
.factory
.msncon
.contactStatusChanged(userHandle
)
437 def contactOffline(self
, userHandle
):
438 LogEvent(INFO
, self
.factory
.msncon
.ident
)
439 self
.factory
.msncon
.contactStatusChanged(userHandle
)
441 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
442 LogEvent(INFO
, self
.factory
.msncon
.ident
)
443 sb
= self
.factory
.msncon
.switchboardSessions
.get(userHandle
)
444 if sb
and sb
.transport
:
445 sb
.transport
.loseConnection()
446 sb
= OneSwitchboardSession(self
.factory
.msncon
, userHandle
)
447 self
.factory
.msncon
.switchboardSessions
[userHandle
] = sb
448 sb
.connectReply(host
, port
, key
, sessionID
)
450 def multipleLogin(self
):
451 LogEvent(INFO
, self
.factory
.msncon
.ident
)
452 self
.factory
.msncon
.multipleLogin()
454 def serverGoingDown(self
):
455 LogEvent(INFO
, self
.factory
.msncon
.ident
)
456 self
.factory
.msncon
.serverGoingDown()
460 class SwitchboardSessionBase(msn
.SwitchboardClient
):
461 def __init__(self
, msncon
):
462 msn
.SwitchboardClient
.__init
__(self
)
464 self
.msnobj
= msncon
.notificationClient
.msnobj
465 self
.userHandle
= msncon
.username
466 self
.ident
= (msncon
.ident
, "INVALID!!")
467 self
.messageBuffer
= []
471 def connectionLost(self
, reason
):
472 msn
.SwitchboardClient
.connectionLost(self
, reason
)
473 LogEvent(INFO
, self
.ident
)
477 self
.ident
= (self
.ident
[0], self
.ident
[1], "Disconnected!")
480 LogEvent(INFO
, self
.ident
)
485 LogEvent(INFO
, self
.ident
)
487 def sbRequestAccepted((host
, port
, key
)):
488 LogEvent(INFO
, self
.ident
)
491 factory
= ClientFactory()
492 factory
.buildProtocol
= lambda addr
: self
493 reactor
.connectTCP(host
, port
, factory
)
494 def sbRequestFailed(ignored
=None):
495 LogEvent(INFO
, self
.ident
)
496 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
497 d
= self
.msncon
.notificationClient
.requestSwitchboardServer()
498 d
.addCallbacks(sbRequestAccepted
, sbRequestFailed
)
500 def connectReply(self
, host
, port
, key
, sessionID
):
501 LogEvent(INFO
, self
.ident
)
504 self
.sessionID
= sessionID
506 factory
= ClientFactory()
507 factory
.buildProtocol
= lambda addr
: self
508 reactor
.connectTCP(host
, port
, factory
)
510 def flushBuffer(self
):
511 for message
, noerror
in self
.messageBuffer
[:]:
512 self
.messageBuffer
.remove((message
, noerror
))
513 self
.sendMessage(message
, noerror
)
514 for f
in self
.funcBuffer
[:]:
515 self
.funcBuffer
.remove(f
)
518 def failedMessage(self
, *ignored
):
519 raise NotImplementedError
521 def sendClientCaps(self
):
522 message
= msn
.MSNMessage()
523 message
.setHeader("Content-Type", "text/x-clientcaps")
524 message
.setHeader("Client-Name", "PyMSNt")
525 if hasattr(self
.msncon
, "jabberID"):
526 message
.setHeader("JabberID", str(self
.msncon
.jabberID
))
527 self
.sendMessage(message
)
529 def sendMessage(self
, message
, noerror
=False):
530 # Check to make sure that clientcaps only gets sent after
531 # the first text type message.
532 if isinstance(message
, msn
.MSNMessage
) and message
.getHeader("Content-Type").startswith("text"):
533 self
.sendMessage
= self
.sendMessageReal
534 self
.sendClientCaps()
535 return self
.sendMessage(message
, noerror
)
537 return self
.sendMessageReal(message
, noerror
)
539 def sendMessageReal(self
, text
, noerror
=False):
540 if not isinstance(text
, basestring
):
541 msn
.SwitchboardClient
.sendMessage(self
, text
)
544 self
.messageBuffer
.append((text
, noerror
))
546 LogEvent(INFO
, self
.ident
)
547 text
= str(text
.replace("\n", "\r\n").encode("utf-8"))
548 def failedMessage(ignored
):
550 self
.failedMessage(text
)
552 if len(text
) < MSNConnection
.MAXMESSAGESIZE
:
553 message
= msn
.MSNMessage(message
=text
)
554 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
556 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
558 d
.addCallback(failedMessage
)
561 chunks
= int(math
.ceil(len(text
) / float(MSNConnection
.MAXMESSAGESIZE
)))
563 guid
= msn
.random_guid()
564 while chunk
< chunks
:
565 offset
= chunk
* MSNConnection
.MAXMESSAGESIZE
566 message
= msn
.MSNMessage(message
=text
[offset
: offset
+ MSNConnection
.MAXMESSAGESIZE
])
567 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
568 message
.setHeader("Message-ID", guid
)
570 message
.setHeader("Chunks", str(chunks
))
572 message
.delHeader("MIME-Version")
573 message
.delHeader("Content-Type")
574 message
.setHeader("Chunk", str(chunk
))
576 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
578 d
.addCallback(failedMessage
)
583 class MultiSwitchboardSession(SwitchboardSessionBase
):
584 """ Create one of me to chat to multiple contacts """
586 def __init__(self
, msncon
):
587 """ Automatically creates a new switchboard connection to the server """
588 SwitchboardSessionBase
.__init
__(self
, msncon
)
589 self
.ident
= (self
.msncon
.ident
, repr(self
))
590 self
.contactCount
= 0
591 self
.groupchat
= None
594 def failedMessage(self
, text
):
595 self
.groupchat
.gotMessage("BOUNCE", text
)
597 def sendMessage(self
, text
, noerror
=False):
598 """ Used to send a mesage to the groupchat. Can be called immediately
599 after instantiation. """
600 if self
.contactCount
> 0:
601 SwitchboardSessionBase
.sendMessage(self
, text
, noerror
)
603 #self.messageBuffer.append((message, noerror))
604 pass # They're sending messages to an empty room. Ignore.
606 def inviteUser(self
, userHandle
):
607 """ Used to invite a contact to the groupchat. Can be called immediately
608 after instantiation. """
609 userHandle
= str(userHandle
)
611 LogEvent(INFO
, self
.ident
, "immediate")
612 msn
.SwitchboardClient
.inviteUser(self
, userHandle
)
614 LogEvent(INFO
, self
.ident
, "pending")
615 self
.funcBuffer
.append(lambda: msn
.SwitchboardClient
.inviteUser(self
, userHandle
))
617 def gotMessage(self
, message
):
618 self
.groupchat
.gotMessage(message
.userHandle
, message
.getMessage())
620 def userJoined(self
, userHandle
, screenName
=''):
621 LogEvent(INFO
, self
.ident
)
622 self
.contactCount
+= 1
623 self
.groupchat
.contactJoined(userHandle
)
625 def userLeft(self
, userHandle
):
626 LogEvent(INFO
, self
.ident
)
627 self
.contactCount
-= 1
628 self
.groupchat
.contactLeft(userHandle
)
632 class OneSwitchboardSession(SwitchboardSessionBase
):
633 def __init__(self
, msncon
, remoteUser
):
634 SwitchboardSessionBase
.__init
__(self
, msncon
)
635 self
.remoteUser
= str(remoteUser
)
636 self
.ident
= (self
.msncon
.ident
, self
.remoteUser
)
637 self
.chattingUsers
= []
640 def connectionLost(self
, reason
):
642 self
.timeout
.cancel()
644 for message
, noerror
in self
.messageBuffer
:
646 self
.failedMessage(message
)
647 self
.messageBuffer
= []
648 SwitchboardSessionBase
.connectionLost(self
, reason
)
651 LogEvent(INFO
, self
.ident
)
653 for user
in self
.chattingUsers
:
654 self
.userJoined(user
)
656 self
.timeout
.cancel()
660 def _switchToMulti(self
, userHandle
):
661 LogEvent(INFO
, self
.ident
)
662 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
663 self
.__class
__ = MultiSwitchboardSession
665 self
.contactCount
= 0
666 self
.msncon
.gotGroupchat(self
, userHandle
)
667 assert self
.groupchat
669 def failedMessage(self
, text
):
670 self
.msncon
.failedMessage(self
.remoteUser
, text
)
674 LogEvent(INFO
, self
.ident
)
676 def failCB(arg
=None):
677 if not (self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
)):
679 LogEvent(INFO
, self
.ident
, "User has not joined after 30 seconds.")
680 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
682 self
.transport
.loseConnection()
683 d
= self
.inviteUser(self
.remoteUser
)
685 self
.timeout
= reactor
.callLater(30.0, failCB
)
689 def gotChattingUsers(self
, users
):
690 for userHandle
in users
.keys():
691 self
.chattingUsers
.append(userHandle
)
693 def userJoined(self
, userHandle
, screenName
=''):
694 LogEvent(INFO
, self
.ident
)
697 if userHandle
!= self
.remoteUser
:
698 # Another user has joined, so we now have three participants.
699 remoteUser
= self
.remoteUser
700 self
._switchToMulti
(remoteUser
)
701 self
.userJoined(remoteUser
)
702 self
.userJoined(userHandle
)
704 def updateAvatarCB((imageData
, )):
706 self
.msncon
.gotAvatarImageData(self
.remoteUser
, imageData
)
707 d
= self
.sendAvatarRequest()
709 d
.addCallback(updateAvatarCB
)
711 def userLeft(self
, userHandle
):
713 if userHandle
== self
.remoteUser
:
714 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
715 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
716 reactor
.callLater(0, wait
) # Make sure this is handled after everything else
718 def gotMessage(self
, message
):
719 LogEvent(INFO
, self
.ident
)
720 cTypes
= [s
.strip() for s
in message
.getHeader("Content-Type").split(';')]
721 if "text/plain" == cTypes
[0]:
723 if len(cTypes
) > 1 and cTypes
[1].lower().find("utf-8") >= 0:
724 text
= message
.getMessage().decode("utf-8")
726 text
= message
.getMessage()
727 self
.msncon
.gotMessage(self
.remoteUser
, text
)
729 self
.msncon
.gotMessage(self
.remoteUser
, "A message was lost.")
731 elif "text/x-clientcaps" == cTypes
[0]:
732 if message
.hasHeader("JabberID"):
733 jid
= message
.getHeader("JabberID")
734 self
.msncon
.userMapping(message
.userHandle
, jid
)
736 LogEvent(INFO
, self
.ident
, "Discarding unknown message type.")
738 def gotFileReceive(self
, fileReceive
):
739 LogEvent(INFO
, self
.ident
)
740 self
.msncon
.gotFileReceive(fileReceive
)
742 def gotContactTyping(self
, message
):
743 LogEvent(INFO
, self
.ident
)
744 self
.msncon
.gotContactTyping(message
.userHandle
)
746 def sendTypingNotification(self
):
747 LogEvent(INFO
, self
.ident
)
749 msn
.SwitchboardClient
.sendTypingNotification(self
)
751 CAPS
= msn
.MSNContact
.MSNC1 | msn
.MSNContact
.MSNC2 | msn
.MSNContact
.MSNC3 | msn
.MSNContact
.MSNC4
752 def sendAvatarRequest(self
):
753 if not self
.ready
: return
754 msnContacts
= self
.msncon
.getContacts()
755 if not msnContacts
: return
756 msnContact
= msnContacts
.getContact(self
.remoteUser
)
757 if not (msnContact
and msnContact
.caps
& self
.CAPS
and msnContact
.msnobj
): return
758 if msnContact
.msnobjGot
: return
759 msnContact
.msnobjGot
= True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
760 return msn
.SwitchboardClient
.sendAvatarRequest(self
, msnContact
)
762 def sendFile(self
, msnContact
, filename
, filesize
):
763 def doSendFile(ignored
=None):
764 d
.callback(msn
.SwitchboardClient
.sendFile(self
, msnContact
, filename
, filesize
))
767 reactor
.callLater(0, doSendFile
)
769 self
.funcBuffer
.append(doSendFile
)