]>
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
:
234 if self
.notificationFactory
:
235 self
.notificationFactory
.stopTrying()
236 self
.notificationFactory
.msncon
= None
238 for sbs
in self
.switchboardSessions
.values():
239 if hasattr(sbs
, "transport") and sbs
.transport
:
240 sbs
.transport
.loseConnection()
241 self
.switchboardSessions
= {}
243 self
.timeout
.cancel()
245 LogEvent(INFO
, self
.ident
)
249 def connectionFailed(self
, reason
=''):
250 """ Called when the connection to the server failed. """
252 def loginFailed(self
, reason
=''):
253 """ Called when the account could not be logged in. """
255 def connectionLost(self
, reason
=''):
256 """ Called when we are disconnected. """
258 def multipleLogin(self
):
259 """ Called when the server says there has been another login
260 for this account. """
262 def serverGoingDown(self
):
263 """ Called when the server says that it will be going down. """
265 def accountNotVerified(self
):
266 """ Called if this passport has not been verified. Certain
267 functions are not available. """
269 def userMapping(self
, passport
, jid
):
270 """ Called when it is brought to our attention that one of the
271 MSN contacts has a Jabber ID. You should communicate with Jabber. """
274 """ Called when we have authenticated, but before we receive
275 the contact list. """
277 def listSynchronized(self
):
278 """ Called when we have received the contact list. All methods
279 in this class are now valid. """
281 def ourStatusChanged(self
, statusCode
, screenName
, personal
):
282 """ Called when the user's status has changed. """
284 def gotMessage(self
, userHandle
, text
):
285 """ Called when a contact sends us a message """
287 def gotGroupchat(self
, msnGroupchat
, userHandle
):
288 """ Called when a conversation with more than one contact begins.
289 userHandle is the person who invited us.
290 The overriding method is expected to set msnGroupchat.groupchat to an object
291 that implements the following methods:
292 contactJoined(userHandle)
293 contactLeft(userHandle)
294 gotMessage(userHandle, text)
296 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
299 def gotContactTyping(self
, userHandle
):
300 """ Called when a contact sends typing notification.
301 Will be called once every 5 seconds. """
303 def failedMessage(self
, userHandle
, text
):
304 """ Called when a message we sent has been bounced back. """
306 def contactAvatarChanged(self
, userHandle
, hash):
307 """ Called when we receive a changed avatar hash for a contact.
308 You should call sendAvatarRequest(). """
310 def contactStatusChanged(self
, userHandle
):
311 """ Called when we receive status information for a contact. """
313 def gotFileReceive(self
, fileReceive
):
314 """ Called when a contact sends the user a file.
315 Call accept(fileHandle) or reject() on the object. """
317 def contactAddedMe(self
, userHandle
):
318 """ Called when a contact adds the user to their list. """
320 def contactRemovedMe(self
, userHandle
):
321 """ Called when a contact removes the user from their list. """
323 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
324 """ Received at login to tell about the user's Hotmail status """
326 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
327 """ Received in realtime whenever an email comes into the hotmail account """
329 def gotMSNAlert(self
, body
, action
, subscr
):
330 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
331 text of the alert. 'action' is a url for more information,
332 'subscr' is a url to modify your your alerts subscriptions. """
334 def gotAvatarImageData(self
, userHandle
, imageData
):
335 """ An contact's avatar has been received because a switchboard
336 session with them was started. """
344 self
.avatarImageData
= ""
345 self
.addContacts
= []
346 self
.remContacts
= []
348 def send(self
, msncon
):
349 if self
.avatarImageData
:
350 msncon
.notificationClient
.changeAvatar(self
.avatarImageData
, push
=False)
351 if self
.screenName
or self
.statusCode
or self
.personal
:
352 msncon
.changeStatus(self
.statusCode
, self
.screenName
, self
.personal
)
353 for listType
, userHandle
in self
.addContacts
:
354 msncon
.addContact(listType
, userHandle
)
355 for listType
, userHandle
in self
.remContacts
:
356 msncon
.remContact(listType
, userHandle
)
360 class DispatchClient(msn
.DispatchClient
):
361 def gotNotificationReferral(self
, host
, port
):
363 self
.factory
.d
= None
364 if not d
or d
.called
:
365 return # Too slow! We've already timed out
366 d
.callback((host
, port
))
369 class NotificationClient(msn
.NotificationClient
):
370 def doDisconnect(self
, *args
):
371 if hasattr(self
, "transport") and self
.transport
:
372 self
.transport
.loseConnection()
374 def loginFailure(self
, message
):
375 self
.factory
.msncon
.loginFailed(message
)
377 def loggedIn(self
, userHandle
, verified
):
378 LogEvent(INFO
, self
.factory
.msncon
.ident
)
379 msn
.NotificationClient
.loggedIn(self
, userHandle
, verified
)
380 self
.factory
.msncon
._notificationClientReady
(self
)
381 self
.factory
.msncon
.loggedIn()
383 self
.factory
.msncon
.accountNotVerified()
386 msn
.NotificationClient
.logOut(self
)
387 # If we explicitly log out, then all of these events
389 self
.loginFailure
= self
.doDisconnect
390 self
.loggedIn
= self
.doDisconnect
391 self
.connectionLost
= self
.doDisconnect
393 def connectionLost(self
, reason
):
394 if not self
.factory
.msncon
:
395 # If MSNConnection.logOut is called before _notificationClientReady
399 LogEvent(INFO
, self
.factory
.msncon
.ident
)
400 msn
.NotificationClient
.connectionLost(self
, reason
)
401 if self
.factory
.maxRetries
> self
.factory
.retries
:
402 self
.factory
.stopTrying()
403 self
.factory
.msncon
.connectionLost(reason
)
404 # Make sure this event is handled after any others
405 reactor
.callLater(0, wait
)
407 def gotMSNAlert(self
, body
, action
, subscr
):
408 LogEvent(INFO
, self
.factory
.msncon
.ident
)
409 self
.factory
.msncon
.gotMSNAlert(body
, action
, subscr
)
411 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
412 LogEvent(INFO
, self
.factory
.msncon
.ident
)
413 self
.factory
.msncon
.gotInitialEmailNotification(inboxunread
, foldersunread
)
415 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
416 LogEvent(INFO
, self
.factory
.msncon
.ident
)
417 self
.factory
.msncon
.gotRealtimeEmailNotification(mailfrom
, fromaddr
, subject
)
419 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
420 LogEvent(INFO
, self
.factory
.msncon
.ident
)
421 self
.factory
.msncon
.contactAddedMe(userHandle
)
423 def userRemovedMe(self
, userHandle
):
424 LogEvent(INFO
, self
.factory
.msncon
.ident
)
425 self
.factory
.msncon
.contactRemovedMe(userHandle
)
427 def listSynchronized(self
, *args
):
428 LogEvent(INFO
, self
.factory
.msncon
.ident
)
429 self
.factory
.msncon
._sendSavedEvents
()
430 self
.factory
.msncon
.listSynchronized()
432 def contactAvatarChanged(self
, userHandle
, hash):
433 LogEvent(INFO
, self
.factory
.msncon
.ident
)
434 self
.factory
.msncon
.contactAvatarChanged(userHandle
, hash)
436 def gotContactStatus(self
, userHandle
, statusCode
, screenName
):
437 LogEvent(INFO
, self
.factory
.msncon
.ident
)
438 self
.factory
.msncon
.contactStatusChanged(userHandle
)
440 def contactStatusChanged(self
, userHandle
, statusCode
, screenName
):
441 LogEvent(INFO
, self
.factory
.msncon
.ident
)
442 self
.factory
.msncon
.contactStatusChanged(userHandle
)
444 def contactPersonalChanged(self
, userHandle
, personal
):
445 LogEvent(INFO
, self
.factory
.msncon
.ident
)
446 self
.factory
.msncon
.contactStatusChanged(userHandle
)
448 def contactOffline(self
, userHandle
):
449 LogEvent(INFO
, self
.factory
.msncon
.ident
)
450 self
.factory
.msncon
.contactStatusChanged(userHandle
)
452 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
453 LogEvent(INFO
, self
.factory
.msncon
.ident
)
454 sb
= self
.factory
.msncon
.switchboardSessions
.get(userHandle
)
455 if sb
and sb
.transport
:
456 sb
.transport
.loseConnection()
457 sb
= OneSwitchboardSession(self
.factory
.msncon
, userHandle
)
458 self
.factory
.msncon
.switchboardSessions
[userHandle
] = sb
459 sb
.connectReply(host
, port
, key
, sessionID
)
461 def multipleLogin(self
):
462 LogEvent(INFO
, self
.factory
.msncon
.ident
)
463 self
.factory
.msncon
.multipleLogin()
465 def serverGoingDown(self
):
466 LogEvent(INFO
, self
.factory
.msncon
.ident
)
467 self
.factory
.msncon
.serverGoingDown()
471 class SwitchboardSessionBase(msn
.SwitchboardClient
):
472 def __init__(self
, msncon
):
473 msn
.SwitchboardClient
.__init
__(self
)
475 self
.msnobj
= msncon
.notificationClient
.msnobj
476 self
.userHandle
= msncon
.username
477 self
.ident
= (msncon
.ident
, "INVALID!!")
478 self
.messageBuffer
= []
482 def connectionLost(self
, reason
):
483 msn
.SwitchboardClient
.connectionLost(self
, reason
)
484 LogEvent(INFO
, self
.ident
)
488 self
.ident
= (self
.ident
[0], self
.ident
[1], "Disconnected!")
491 LogEvent(INFO
, self
.ident
)
496 LogEvent(INFO
, self
.ident
)
498 def sbRequestAccepted((host
, port
, key
)):
499 LogEvent(INFO
, self
.ident
)
502 factory
= ClientFactory()
503 factory
.buildProtocol
= lambda addr
: self
504 reactor
.connectTCP(host
, port
, factory
)
505 def sbRequestFailed(ignored
=None):
506 LogEvent(INFO
, self
.ident
)
507 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
508 d
= self
.msncon
.notificationClient
.requestSwitchboardServer()
509 d
.addCallbacks(sbRequestAccepted
, sbRequestFailed
)
511 def connectReply(self
, host
, port
, key
, sessionID
):
512 LogEvent(INFO
, self
.ident
)
515 self
.sessionID
= sessionID
517 factory
= ClientFactory()
518 factory
.buildProtocol
= lambda addr
: self
519 reactor
.connectTCP(host
, port
, factory
)
521 def flushBuffer(self
):
522 for message
, noerror
in self
.messageBuffer
[:]:
523 self
.messageBuffer
.remove((message
, noerror
))
524 self
.sendMessage(message
, noerror
)
525 for f
in self
.funcBuffer
[:]:
526 self
.funcBuffer
.remove(f
)
529 def failedMessage(self
, *ignored
):
530 raise NotImplementedError
532 def sendClientCaps(self
):
533 message
= msn
.MSNMessage()
534 message
.setHeader("Content-Type", "text/x-clientcaps")
535 message
.setHeader("Client-Name", "PyMSNt")
536 if hasattr(self
.msncon
, "jabberID"):
537 message
.setHeader("JabberID", str(self
.msncon
.jabberID
))
538 self
.sendMessage(message
)
540 def sendMessage(self
, message
, noerror
=False):
541 # Check to make sure that clientcaps only gets sent after
542 # the first text type message.
543 if isinstance(message
, msn
.MSNMessage
) and message
.getHeader("Content-Type").startswith("text"):
544 self
.sendMessage
= self
.sendMessageReal
545 self
.sendClientCaps()
546 return self
.sendMessage(message
, noerror
)
548 return self
.sendMessageReal(message
, noerror
)
550 def sendMessageReal(self
, text
, noerror
=False):
551 if not isinstance(text
, basestring
):
552 msn
.SwitchboardClient
.sendMessage(self
, text
)
555 self
.messageBuffer
.append((text
, noerror
))
557 LogEvent(INFO
, self
.ident
)
558 text
= str(text
.replace("\n", "\r\n").encode("utf-8"))
559 def failedMessage(ignored
):
561 self
.failedMessage(text
)
563 if len(text
) < MSNConnection
.MAXMESSAGESIZE
:
564 message
= msn
.MSNMessage(message
=text
)
565 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
567 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
569 d
.addCallbacks(failedMessage
, failedMessage
)
572 chunks
= int(math
.ceil(len(text
) / float(MSNConnection
.MAXMESSAGESIZE
)))
574 guid
= msn
.random_guid()
575 while chunk
< chunks
:
576 offset
= chunk
* MSNConnection
.MAXMESSAGESIZE
577 message
= msn
.MSNMessage(message
=text
[offset
: offset
+ MSNConnection
.MAXMESSAGESIZE
])
578 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
579 message
.setHeader("Message-ID", guid
)
581 message
.setHeader("Chunks", str(chunks
))
583 message
.delHeader("MIME-Version")
584 message
.delHeader("Content-Type")
585 message
.setHeader("Chunk", str(chunk
))
587 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
589 d
.addCallbacks(failedMessage
, failedMessage
)
594 class MultiSwitchboardSession(SwitchboardSessionBase
):
595 """ Create one of me to chat to multiple contacts """
597 def __init__(self
, msncon
):
598 """ Automatically creates a new switchboard connection to the server """
599 SwitchboardSessionBase
.__init
__(self
, msncon
)
600 self
.ident
= (self
.msncon
.ident
, repr(self
))
601 self
.contactCount
= 0
602 self
.groupchat
= None
605 def failedMessage(self
, text
):
606 self
.groupchat
.gotMessage("BOUNCE", text
)
608 def sendMessage(self
, text
, noerror
=False):
609 """ Used to send a mesage to the groupchat. Can be called immediately
610 after instantiation. """
611 if self
.contactCount
> 0:
612 SwitchboardSessionBase
.sendMessage(self
, text
, noerror
)
614 #self.messageBuffer.append((message, noerror))
615 pass # They're sending messages to an empty room. Ignore.
617 def inviteUser(self
, userHandle
):
618 """ Used to invite a contact to the groupchat. Can be called immediately
619 after instantiation. """
620 userHandle
= str(userHandle
)
622 LogEvent(INFO
, self
.ident
, "immediate")
623 msn
.SwitchboardClient
.inviteUser(self
, userHandle
)
625 LogEvent(INFO
, self
.ident
, "pending")
626 self
.funcBuffer
.append(lambda: msn
.SwitchboardClient
.inviteUser(self
, userHandle
))
628 def gotMessage(self
, message
):
629 self
.groupchat
.gotMessage(message
.userHandle
, message
.getMessage())
631 def userJoined(self
, userHandle
, screenName
=''):
632 LogEvent(INFO
, self
.ident
)
633 self
.contactCount
+= 1
634 self
.groupchat
.contactJoined(userHandle
)
636 def userLeft(self
, userHandle
):
637 LogEvent(INFO
, self
.ident
)
638 self
.contactCount
-= 1
639 self
.groupchat
.contactLeft(userHandle
)
643 class OneSwitchboardSession(SwitchboardSessionBase
):
644 def __init__(self
, msncon
, remoteUser
):
645 SwitchboardSessionBase
.__init
__(self
, msncon
)
646 self
.remoteUser
= str(remoteUser
)
647 self
.ident
= (self
.msncon
.ident
, self
.remoteUser
)
648 self
.chattingUsers
= []
651 def connectionLost(self
, reason
):
653 self
.timeout
.cancel()
655 for message
, noerror
in self
.messageBuffer
:
657 self
.failedMessage(message
)
658 self
.messageBuffer
= []
660 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
661 # Unexpected disconnection. Must remove us from msncon
662 self
.msncon
.switchboardSessions
.pop(self
.remoteUser
)
664 SwitchboardSessionBase
.connectionLost(self
, reason
)
667 LogEvent(INFO
, self
.ident
)
669 for user
in self
.chattingUsers
:
670 self
.userJoined(user
)
672 self
.timeout
.cancel()
676 def _switchToMulti(self
, userHandle
):
677 LogEvent(INFO
, self
.ident
)
678 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
679 self
.__class
__ = MultiSwitchboardSession
681 self
.contactCount
= 0
682 self
.msncon
.gotGroupchat(self
, userHandle
)
683 assert self
.groupchat
685 def failedMessage(self
, text
):
686 self
.msncon
.failedMessage(self
.remoteUser
, text
)
690 LogEvent(INFO
, self
.ident
)
692 def failCB(arg
=None):
693 if not (self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
)):
695 LogEvent(INFO
, self
.ident
, "User has not joined after 30 seconds.")
696 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
698 self
.transport
.loseConnection()
699 d
= self
.inviteUser(self
.remoteUser
)
701 self
.timeout
= reactor
.callLater(30.0, failCB
)
705 def gotChattingUsers(self
, users
):
706 for userHandle
in users
.keys():
707 self
.chattingUsers
.append(userHandle
)
709 def userJoined(self
, userHandle
, screenName
=''):
710 LogEvent(INFO
, self
.ident
)
713 if userHandle
!= self
.remoteUser
:
714 # Another user has joined, so we now have three participants.
715 remoteUser
= self
.remoteUser
716 self
._switchToMulti
(remoteUser
)
717 self
.userJoined(remoteUser
)
718 self
.userJoined(userHandle
)
720 def updateAvatarCB((imageData
, )):
722 self
.msncon
.gotAvatarImageData(self
.remoteUser
, imageData
)
723 d
= self
.sendAvatarRequest()
725 d
.addCallback(updateAvatarCB
)
727 def userLeft(self
, userHandle
):
729 if userHandle
== self
.remoteUser
:
730 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
731 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
732 reactor
.callLater(0, wait
) # Make sure this is handled after everything else
734 def gotMessage(self
, message
):
735 LogEvent(INFO
, self
.ident
)
736 cTypes
= [s
.strip() for s
in message
.getHeader("Content-Type").split(';')]
737 if "text/plain" == cTypes
[0]:
739 if len(cTypes
) > 1 and cTypes
[1].lower().find("utf-8") >= 0:
740 text
= message
.getMessage().decode("utf-8")
742 text
= message
.getMessage()
743 self
.msncon
.gotMessage(self
.remoteUser
, text
)
744 except UnicodeDecodeError:
745 LogEvent(WARN
, self
.ident
, "Message lost!")
746 self
.msncon
.gotMessage(self
.remoteUser
, "A message was lost.")
748 elif "text/x-clientcaps" == cTypes
[0]:
749 if message
.hasHeader("JabberID"):
750 jid
= message
.getHeader("JabberID")
751 self
.msncon
.userMapping(message
.userHandle
, jid
)
753 LogEvent(INFO
, self
.ident
, "Discarding unknown message type.")
755 def gotFileReceive(self
, fileReceive
):
756 LogEvent(INFO
, self
.ident
)
757 self
.msncon
.gotFileReceive(fileReceive
)
759 def gotContactTyping(self
, message
):
760 LogEvent(INFO
, self
.ident
)
761 self
.msncon
.gotContactTyping(message
.userHandle
)
763 def sendTypingNotification(self
):
764 LogEvent(INFO
, self
.ident
)
766 msn
.SwitchboardClient
.sendTypingNotification(self
)
768 CAPS
= msn
.MSNContact
.MSNC1 | msn
.MSNContact
.MSNC2 | msn
.MSNContact
.MSNC3 | msn
.MSNContact
.MSNC4
769 def sendAvatarRequest(self
):
770 if not self
.ready
: return
771 msnContacts
= self
.msncon
.getContacts()
772 if not msnContacts
: return
773 msnContact
= msnContacts
.getContact(self
.remoteUser
)
774 if not (msnContact
and msnContact
.caps
& self
.CAPS
and msnContact
.msnobj
): return
775 if msnContact
.msnobjGot
: return
776 msnContact
.msnobjGot
= True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
777 return msn
.SwitchboardClient
.sendAvatarRequest(self
, msnContact
)
779 def sendFile(self
, msnContact
, filename
, filesize
):
780 def doSendFile(ignored
=None):
781 d
.callback(msn
.SwitchboardClient
.sendFile(self
, msnContact
, filename
, filesize
))
784 reactor
.callLater(0, doSendFile
)
786 self
.funcBuffer
.append(doSendFile
)