]>
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
):
54 dispatchFactory
.d
= None
56 d
.errback(Exception("Timeout"))
57 self
.logOut() # Clean up everything
58 self
.timeout
= reactor
.callLater(30, timeout
)
59 dispatchFactory
= msn
.DispatchFactory()
60 dispatchFactory
.userHandle
= self
.username
61 dispatchFactory
.protocol
= DispatchClient
64 d
.addCallbacks(self
._gotNotificationReferral
, self
.connectionFailed
)
65 self
.connectors
.append(reactor
.connectTCP("messenger.hotmail.com", 1863, dispatchFactory
))
66 LogEvent(INFO
, self
.ident
)
68 def _gotNotificationReferral(self
, (host
, port
)):
71 # Create the NotificationClient
72 self
.notificationFactory
= msn
.NotificationFactory()
73 self
.notificationFactory
.userHandle
= self
.username
74 self
.notificationFactory
.password
= self
.password
75 self
.notificationFactory
.msncon
= self
76 self
.notificationFactory
.protocol
= NotificationClient
77 self
.connectors
.append(reactor
.connectTCP(host
, port
, self
.notificationFactory
))
78 LogEvent(INFO
, self
.ident
)
80 def _sendSavedEvents(self
):
81 self
.savedEvents
.send(self
)
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)
180 # Save the avatar for reuse on disconnection
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 for Python's limited scope :(
198 def cb(ignored
=None):
200 if changeCount
[0] == 3:
201 self
.ourStatusChanged(statusCode
, screenName
, personal
)
202 def errcb(ignored
=None):
203 pass # FIXME, should we do something here?
204 LogEvent(INFO
, self
.ident
)
205 self
.notificationClient
.changeStatus(statusCode
.encode("utf-8")).addCallbacks(cb
, errcb
)
206 self
.notificationClient
.changeScreenName(screenName
.encode("utf-8")).addCallbacks(cb
, errcb
)
207 self
.notificationClient
.changePersonalMessage(personal
.encode("utf-8")).addCallbacks(cb
, errcb
)
208 # Remember the saved status
209 self
.savedEvents
.statusCode
= statusCode
210 self
.savedEvents
.screenName
= screenName
211 self
.savedEvents
.personal
= personal
213 def addContact(self
, listType
, userHandle
):
214 """ See msn.NotificationClient.addContact """
215 if self
.notificationClient
:
216 return self
.notificationClient
.addContact(listType
, str(userHandle
))
218 self
.savedEvents
.addContacts
.append((listType
, str(userHandle
)))
220 def remContact(self
, listType
, userHandle
):
221 """ See msn.NotificationClient.remContact """
222 if self
.notificationClient
:
223 return self
.notificationClient
.remContact(listType
, str(userHandle
))
225 self
.savedEvents
.remContacts
.append((listType
, str(userHandle
)))
228 """ Shuts down the whole connection. Don't try to call any
229 other methods after this one. Except maybe connect() """
230 if self
.notificationClient
:
231 self
.notificationClient
.logOut()
232 for c
in self
.connectors
:
235 if self
.notificationFactory
:
236 self
.notificationFactory
.stopTrying()
237 self
.notificationFactory
.msncon
= None
238 self
.notificationFactory
= None
239 for sbs
in self
.switchboardSessions
.values():
240 if hasattr(sbs
, "transport") and sbs
.transport
:
241 sbs
.transport
.loseConnection()
242 self
.switchboardSessions
= {}
244 self
.timeout
.cancel()
246 LogEvent(INFO
, self
.ident
)
250 def connectionFailed(self
, reason
=''):
251 """ Called when the connection to the server failed. """
253 def loginFailed(self
, reason
=''):
254 """ Called when the account could not be logged in. """
256 def connectionLost(self
, reason
=''):
257 """ Called when we are disconnected. """
259 def multipleLogin(self
):
260 """ Called when the server says there has been another login
261 for this account. """
263 def serverGoingDown(self
):
264 """ Called when the server says that it will be going down. """
266 def accountNotVerified(self
):
267 """ Called if this passport has not been verified. Certain
268 functions are not available. """
270 def userMapping(self
, passport
, jid
):
271 """ Called when it is brought to our attention that one of the
272 MSN contacts has a Jabber ID. You should communicate with Jabber. """
275 """ Called when we have authenticated, but before we receive
276 the contact list. """
278 def listSynchronized(self
):
279 """ Called when we have received the contact list. All methods
280 in this class are now valid. """
282 def ourStatusChanged(self
, statusCode
, screenName
, personal
):
283 """ Called when the user's status has changed. """
285 def gotMessage(self
, userHandle
, text
):
286 """ Called when a contact sends us a message """
288 def gotGroupchat(self
, msnGroupchat
, userHandle
):
289 """ Called when a conversation with more than one contact begins.
290 userHandle is the person who invited us.
291 The overriding method is expected to set msnGroupchat.groupchat to an object
292 that implements the following methods:
293 contactJoined(userHandle)
294 contactLeft(userHandle)
295 gotMessage(userHandle, text)
297 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
300 def gotContactTyping(self
, userHandle
):
301 """ Called when a contact sends typing notification.
302 Will be called once every 5 seconds. """
304 def failedMessage(self
, userHandle
, text
):
305 """ Called when a message we sent has been bounced back. """
307 def contactAvatarChanged(self
, userHandle
, hash):
308 """ Called when we receive a changed avatar hash for a contact.
309 You should call sendAvatarRequest(). """
311 def contactStatusChanged(self
, userHandle
):
312 """ Called when we receive status information for a contact. """
314 def gotFileReceive(self
, fileReceive
):
315 """ Called when a contact sends the user a file.
316 Call accept(fileHandle) or reject() on the object. """
318 def contactAddedMe(self
, userHandle
):
319 """ Called when a contact adds the user to their list. """
321 def contactRemovedMe(self
, userHandle
):
322 """ Called when a contact removes the user from their list. """
324 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
325 """ Received at login to tell about the user's Hotmail status """
327 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
328 """ Received in realtime whenever an email comes into the hotmail account """
330 def gotMSNAlert(self
, body
, action
, subscr
):
331 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
332 text of the alert. 'action' is a url for more information,
333 'subscr' is a url to modify your your alerts subscriptions. """
335 def gotAvatarImageData(self
, userHandle
, imageData
):
336 """ An contact's avatar has been received because a switchboard
337 session with them was started. """
345 self
.avatarImageData
= ""
346 self
.addContacts
= []
347 self
.remContacts
= []
349 def send(self
, msncon
):
350 if self
.avatarImageData
:
351 msncon
.notificationClient
.changeAvatar(self
.avatarImageData
, push
=False)
352 if self
.screenName
or self
.statusCode
or self
.personal
:
353 msncon
.changeStatus(self
.statusCode
, self
.screenName
, self
.personal
)
354 for listType
, userHandle
in self
.addContacts
:
355 msncon
.addContact(listType
, userHandle
)
356 for listType
, userHandle
in self
.remContacts
:
357 msncon
.remContact(listType
, userHandle
)
361 class DispatchClient(msn
.DispatchClient
):
362 def gotNotificationReferral(self
, host
, port
):
364 self
.factory
.d
= None
365 if not d
or d
.called
:
366 return # Too slow! We've already timed out
367 d
.callback((host
, port
))
370 class NotificationClient(msn
.NotificationClient
):
371 def doDisconnect(self
, *args
):
372 if hasattr(self
, "transport") and self
.transport
:
373 self
.transport
.loseConnection()
375 def loginFailure(self
, message
):
376 self
.factory
.msncon
.loginFailed(message
)
378 def loggedIn(self
, userHandle
, verified
):
379 LogEvent(INFO
, self
.factory
.msncon
.ident
)
380 msn
.NotificationClient
.loggedIn(self
, userHandle
, verified
)
381 self
.factory
.msncon
._notificationClientReady
(self
)
382 self
.factory
.msncon
.loggedIn()
384 self
.factory
.msncon
.accountNotVerified()
387 msn
.NotificationClient
.logOut(self
)
388 # If we explicitly log out, then all of these events
390 self
.loginFailure
= self
.doDisconnect
391 self
.loggedIn
= self
.doDisconnect
392 self
.connectionLost
= lambda reason
: msn
.NotificationClient
.connectionLost(self
, reason
)
394 def connectionLost(self
, reason
):
395 if not self
.factory
.msncon
:
396 # If MSNConnection.logOut is called before _notificationClientReady
400 LogEvent(INFO
, self
.factory
.msncon
.ident
)
401 msn
.NotificationClient
.connectionLost(self
, reason
)
402 if self
.factory
.maxRetries
> self
.factory
.retries
:
403 self
.factory
.stopTrying()
404 self
.factory
.msncon
.connectionLost(reason
)
405 # Make sure this event is handled after any others
406 reactor
.callLater(0, wait
)
408 def gotMSNAlert(self
, body
, action
, subscr
):
409 LogEvent(INFO
, self
.factory
.msncon
.ident
)
410 self
.factory
.msncon
.gotMSNAlert(body
, action
, subscr
)
412 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
413 LogEvent(INFO
, self
.factory
.msncon
.ident
)
414 self
.factory
.msncon
.gotInitialEmailNotification(inboxunread
, foldersunread
)
416 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
417 LogEvent(INFO
, self
.factory
.msncon
.ident
)
418 self
.factory
.msncon
.gotRealtimeEmailNotification(mailfrom
, fromaddr
, subject
)
420 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
421 LogEvent(INFO
, self
.factory
.msncon
.ident
)
422 self
.factory
.msncon
.contactAddedMe(userHandle
)
424 def userRemovedMe(self
, userHandle
):
425 LogEvent(INFO
, self
.factory
.msncon
.ident
)
426 self
.factory
.msncon
.contactRemovedMe(userHandle
)
428 def listSynchronized(self
, *args
):
429 LogEvent(INFO
, self
.factory
.msncon
.ident
)
430 self
.factory
.msncon
._sendSavedEvents
()
431 self
.factory
.msncon
.listSynchronized()
433 def contactAvatarChanged(self
, userHandle
, hash):
434 LogEvent(INFO
, self
.factory
.msncon
.ident
)
435 self
.factory
.msncon
.contactAvatarChanged(userHandle
, hash)
437 def gotContactStatus(self
, userHandle
, statusCode
, screenName
):
438 LogEvent(INFO
, self
.factory
.msncon
.ident
)
439 self
.factory
.msncon
.contactStatusChanged(userHandle
)
441 def contactStatusChanged(self
, userHandle
, statusCode
, screenName
):
442 LogEvent(INFO
, self
.factory
.msncon
.ident
)
443 self
.factory
.msncon
.contactStatusChanged(userHandle
)
445 def contactPersonalChanged(self
, userHandle
, personal
):
446 LogEvent(INFO
, self
.factory
.msncon
.ident
)
447 self
.factory
.msncon
.contactStatusChanged(userHandle
)
449 def contactOffline(self
, userHandle
):
450 LogEvent(INFO
, self
.factory
.msncon
.ident
)
451 self
.factory
.msncon
.contactStatusChanged(userHandle
)
453 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
454 LogEvent(INFO
, self
.factory
.msncon
.ident
)
455 sb
= self
.factory
.msncon
.switchboardSessions
.get(userHandle
)
456 if sb
and sb
.transport
:
457 sb
.transport
.loseConnection()
458 sb
= OneSwitchboardSession(self
.factory
.msncon
, userHandle
)
459 self
.factory
.msncon
.switchboardSessions
[userHandle
] = sb
460 sb
.connectReply(host
, port
, key
, sessionID
)
462 def multipleLogin(self
):
463 LogEvent(INFO
, self
.factory
.msncon
.ident
)
464 self
.factory
.msncon
.multipleLogin()
466 def serverGoingDown(self
):
467 LogEvent(INFO
, self
.factory
.msncon
.ident
)
468 self
.factory
.msncon
.serverGoingDown()
472 class SwitchboardSessionBase(msn
.SwitchboardClient
):
473 def __init__(self
, msncon
):
474 msn
.SwitchboardClient
.__init
__(self
)
476 self
.msnobj
= msncon
.notificationClient
.msnobj
477 self
.userHandle
= msncon
.username
478 self
.ident
= (msncon
.ident
, "INVALID!!")
479 self
.messageBuffer
= []
483 def connectionLost(self
, reason
):
484 msn
.SwitchboardClient
.connectionLost(self
, reason
)
485 LogEvent(INFO
, self
.ident
)
489 self
.ident
= (self
.ident
[0], self
.ident
[1], "Disconnected!")
492 LogEvent(INFO
, self
.ident
)
497 LogEvent(INFO
, self
.ident
)
499 def sbRequestAccepted((host
, port
, key
)):
500 LogEvent(INFO
, self
.ident
)
503 factory
= ClientFactory()
504 factory
.buildProtocol
= lambda addr
: self
505 reactor
.connectTCP(host
, port
, factory
)
506 def sbRequestFailed(ignored
=None):
507 LogEvent(INFO
, self
.ident
)
508 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
509 d
= self
.msncon
.notificationClient
.requestSwitchboardServer()
510 d
.addCallbacks(sbRequestAccepted
, sbRequestFailed
)
512 def connectReply(self
, host
, port
, key
, sessionID
):
513 LogEvent(INFO
, self
.ident
)
516 self
.sessionID
= sessionID
518 factory
= ClientFactory()
519 factory
.buildProtocol
= lambda addr
: self
520 reactor
.connectTCP(host
, port
, factory
)
522 def flushBuffer(self
):
523 for message
, noerror
in self
.messageBuffer
[:]:
524 self
.messageBuffer
.remove((message
, noerror
))
525 self
.sendMessage(message
, noerror
)
526 for f
in self
.funcBuffer
[:]:
527 self
.funcBuffer
.remove(f
)
530 def failedMessage(self
, *ignored
):
531 raise NotImplementedError
533 def sendClientCaps(self
):
534 message
= msn
.MSNMessage()
535 message
.setHeader("Content-Type", "text/x-clientcaps")
536 message
.setHeader("Client-Name", "PyMSNt")
537 if hasattr(self
.msncon
, "jabberID"):
538 message
.setHeader("JabberID", str(self
.msncon
.jabberID
))
539 self
.sendMessage(message
)
541 def sendMessage(self
, message
, noerror
=False):
542 # Check to make sure that clientcaps only gets sent after
543 # the first text type message.
544 if isinstance(message
, msn
.MSNMessage
) and message
.getHeader("Content-Type").startswith("text"):
545 self
.sendMessage
= self
.sendMessageReal
546 self
.sendClientCaps()
547 return self
.sendMessage(message
, noerror
)
549 return self
.sendMessageReal(message
, noerror
)
551 def sendMessageReal(self
, text
, noerror
=False):
552 if not isinstance(text
, basestring
):
553 msn
.SwitchboardClient
.sendMessage(self
, text
)
556 self
.messageBuffer
.append((text
, noerror
))
558 LogEvent(INFO
, self
.ident
)
559 text
= str(text
.replace("\n", "\r\n").encode("utf-8"))
560 def failedMessage(ignored
):
562 self
.failedMessage(text
)
564 if len(text
) < MSNConnection
.MAXMESSAGESIZE
:
565 message
= msn
.MSNMessage(message
=text
)
566 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
568 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
570 d
.addCallbacks(failedMessage
, failedMessage
)
573 chunks
= int(math
.ceil(len(text
) / float(MSNConnection
.MAXMESSAGESIZE
)))
575 guid
= msn
.random_guid()
576 while chunk
< chunks
:
577 offset
= chunk
* MSNConnection
.MAXMESSAGESIZE
578 message
= msn
.MSNMessage(message
=text
[offset
: offset
+ MSNConnection
.MAXMESSAGESIZE
])
579 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
580 message
.setHeader("Message-ID", guid
)
582 message
.setHeader("Chunks", str(chunks
))
584 message
.delHeader("MIME-Version")
585 message
.delHeader("Content-Type")
586 message
.setHeader("Chunk", str(chunk
))
588 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
590 d
.addCallbacks(failedMessage
, failedMessage
)
595 class MultiSwitchboardSession(SwitchboardSessionBase
):
596 """ Create one of me to chat to multiple contacts """
598 def __init__(self
, msncon
):
599 """ Automatically creates a new switchboard connection to the server """
600 SwitchboardSessionBase
.__init
__(self
, msncon
)
601 self
.ident
= (self
.msncon
.ident
, repr(self
))
602 self
.contactCount
= 0
603 self
.groupchat
= None
606 def failedMessage(self
, text
):
607 self
.groupchat
.gotMessage("BOUNCE", text
)
609 def sendMessage(self
, text
, noerror
=False):
610 """ Used to send a mesage to the groupchat. Can be called immediately
611 after instantiation. """
612 if self
.contactCount
> 0:
613 SwitchboardSessionBase
.sendMessage(self
, text
, noerror
)
615 #self.messageBuffer.append((message, noerror))
616 pass # They're sending messages to an empty room. Ignore.
618 def inviteUser(self
, userHandle
):
619 """ Used to invite a contact to the groupchat. Can be called immediately
620 after instantiation. """
621 userHandle
= str(userHandle
)
623 LogEvent(INFO
, self
.ident
, "immediate")
624 msn
.SwitchboardClient
.inviteUser(self
, userHandle
)
626 LogEvent(INFO
, self
.ident
, "pending")
627 self
.funcBuffer
.append(lambda: msn
.SwitchboardClient
.inviteUser(self
, userHandle
))
629 def gotMessage(self
, message
):
630 self
.groupchat
.gotMessage(message
.userHandle
, message
.getMessage())
632 def userJoined(self
, userHandle
, screenName
=''):
633 LogEvent(INFO
, self
.ident
)
634 self
.contactCount
+= 1
635 self
.groupchat
.contactJoined(userHandle
)
637 def userLeft(self
, userHandle
):
638 LogEvent(INFO
, self
.ident
)
639 self
.contactCount
-= 1
640 self
.groupchat
.contactLeft(userHandle
)
644 class OneSwitchboardSession(SwitchboardSessionBase
):
645 def __init__(self
, msncon
, remoteUser
):
646 SwitchboardSessionBase
.__init
__(self
, msncon
)
647 self
.remoteUser
= str(remoteUser
)
648 self
.ident
= (self
.msncon
.ident
, self
.remoteUser
)
649 self
.chattingUsers
= []
652 def connectionLost(self
, reason
):
654 self
.timeout
.cancel()
656 for message
, noerror
in self
.messageBuffer
:
658 self
.failedMessage(message
)
659 self
.messageBuffer
= []
661 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
662 # Unexpected disconnection. Must remove us from msncon
663 self
.msncon
.switchboardSessions
.pop(self
.remoteUser
)
665 SwitchboardSessionBase
.connectionLost(self
, reason
)
668 LogEvent(INFO
, self
.ident
)
670 for user
in self
.chattingUsers
:
671 self
.userJoined(user
)
673 self
.timeout
.cancel()
677 def _switchToMulti(self
, userHandle
):
678 LogEvent(INFO
, self
.ident
)
679 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
680 self
.__class
__ = MultiSwitchboardSession
682 self
.contactCount
= 0
683 self
.msncon
.gotGroupchat(self
, userHandle
)
684 assert self
.groupchat
686 def failedMessage(self
, text
):
687 self
.msncon
.failedMessage(self
.remoteUser
, text
)
691 LogEvent(INFO
, self
.ident
)
693 def failCB(arg
=None):
695 self
.transport
.loseConnection()
696 if not (self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
)):
698 LogEvent(INFO
, self
.ident
, "User has not joined after 30 seconds.")
699 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
700 d
= self
.inviteUser(self
.remoteUser
)
702 self
.timeout
= reactor
.callLater(30.0, failCB
)
706 def gotChattingUsers(self
, users
):
707 for userHandle
in users
.keys():
708 self
.chattingUsers
.append(userHandle
)
710 def userJoined(self
, userHandle
, screenName
=''):
711 LogEvent(INFO
, self
.ident
)
714 if userHandle
!= self
.remoteUser
:
715 # Another user has joined, so we now have three participants.
716 remoteUser
= self
.remoteUser
717 self
._switchToMulti
(remoteUser
)
718 self
.userJoined(remoteUser
)
719 self
.userJoined(userHandle
)
721 def updateAvatarCB((imageData
, )):
723 self
.msncon
.gotAvatarImageData(self
.remoteUser
, imageData
)
724 d
= self
.sendAvatarRequest()
726 d
.addCallback(updateAvatarCB
)
728 def userLeft(self
, userHandle
):
730 if userHandle
== self
.remoteUser
:
731 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
732 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
733 reactor
.callLater(0, wait
) # Make sure this is handled after everything else
735 def gotMessage(self
, message
):
736 LogEvent(INFO
, self
.ident
)
737 cTypes
= [s
.strip() for s
in message
.getHeader("Content-Type").split(';')]
738 if "text/plain" == cTypes
[0]:
740 if len(cTypes
) > 1 and cTypes
[1].lower().find("utf-8") >= 0:
741 text
= message
.getMessage().decode("utf-8")
743 text
= message
.getMessage()
744 self
.msncon
.gotMessage(self
.remoteUser
, text
)
745 except UnicodeDecodeError:
746 LogEvent(WARN
, self
.ident
, "Message lost!")
747 self
.msncon
.gotMessage(self
.remoteUser
, "A message was lost.")
749 elif "text/x-clientcaps" == cTypes
[0]:
750 if message
.hasHeader("JabberID"):
751 jid
= message
.getHeader("JabberID")
752 self
.msncon
.userMapping(message
.userHandle
, jid
)
754 LogEvent(INFO
, self
.ident
, "Discarding unknown message type.")
756 def gotFileReceive(self
, fileReceive
):
757 LogEvent(INFO
, self
.ident
)
758 self
.msncon
.gotFileReceive(fileReceive
)
760 def gotContactTyping(self
, message
):
761 LogEvent(INFO
, self
.ident
)
762 self
.msncon
.gotContactTyping(message
.userHandle
)
764 def sendTypingNotification(self
):
765 LogEvent(INFO
, self
.ident
)
767 msn
.SwitchboardClient
.sendTypingNotification(self
)
769 CAPS
= msn
.MSNContact
.MSNC1 | msn
.MSNContact
.MSNC2 | msn
.MSNContact
.MSNC3 | msn
.MSNContact
.MSNC4
770 def sendAvatarRequest(self
):
771 if not self
.ready
: return
772 msnContacts
= self
.msncon
.getContacts()
773 if not msnContacts
: return
774 msnContact
= msnContacts
.getContact(self
.remoteUser
)
775 if not (msnContact
and msnContact
.caps
& self
.CAPS
and msnContact
.msnobj
): return
776 if msnContact
.msnobjGot
: return
777 msnContact
.msnobjGot
= True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
778 return msn
.SwitchboardClient
.sendAvatarRequest(self
, msnContact
)
780 def sendFile(self
, msnContact
, filename
, filesize
):
781 def doSendFile(ignored
=None):
782 d
.callback(msn
.SwitchboardClient
.sendFile(self
, msnContact
, filename
, filesize
))
785 reactor
.callLater(0, doSendFile
)
787 self
.funcBuffer
.append(doSendFile
)