]>
code.delx.au - pymsnt/blob - src/legacy/msn/msnw.py
1 # Copyright 2004-2006 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
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
28 BINDADDRESS
= "0.0.0.0"
30 def __init__(self
, username
, password
, ident
):
31 """ Connects to the MSN servers.
32 @param username: the MSN passport to connect with.
33 @param password: the password for this account.
34 @param ident: a unique identifier to use in logging.
36 self
.username
= username
37 self
.password
= password
40 self
.notificationFactory
= None
41 self
.notificationClient
= None
43 LogEvent(INFO
, self
.ident
)
46 """ Automatically called by the constructor """
48 self
.switchboardSessions
= {}
49 self
.savedEvents
= SavedEvents() # Save any events that occur before connect
50 self
._getNotificationReferral
()
52 def _getNotificationReferral(self
):
55 dispatchFactory
.d
= None
57 d
.errback(Exception("Timeout"))
58 self
.logOut() # Clean up everything
59 self
.timeout
= reactor
.callLater(30, timeout
)
60 dispatchFactory
= msn
.DispatchFactory()
61 dispatchFactory
.userHandle
= self
.username
62 dispatchFactory
.protocol
= DispatchClient
65 d
.addCallbacks(self
._gotNotificationReferral
, self
.connectionFailed
)
66 self
.connectors
.append(reactor
.connectTCP("messenger.hotmail.com", 1863, dispatchFactory
, bindAddress
=(MSNConnection
.BINDADDRESS
, 0)))
67 LogEvent(INFO
, self
.ident
)
69 def _gotNotificationReferral(self
, (host
, port
)):
72 # Create the NotificationClient
73 self
.notificationFactory
= msn
.NotificationFactory()
74 self
.notificationFactory
.userHandle
= self
.username
75 self
.notificationFactory
.password
= self
.password
76 self
.notificationFactory
.msncon
= self
77 self
.notificationFactory
.protocol
= NotificationClient
78 self
.connectors
.append(reactor
.connectTCP(host
, port
, self
.notificationFactory
, bindAddress
=(MSNConnection
.BINDADDRESS
, 0)))
79 LogEvent(INFO
, self
.ident
)
81 def _sendSavedEvents(self
):
82 self
.savedEvents
.send(self
)
84 def _notificationClientReady(self
, notificationClient
):
85 self
.notificationClient
= notificationClient
87 def _ensureSwitchboardSession(self
, userHandle
):
88 if not self
.switchboardSessions
.has_key(userHandle
):
89 sb
= OneSwitchboardSession(self
, userHandle
)
91 self
.switchboardSessions
[userHandle
] = sb
96 def getContacts(self
):
97 """ Gets the contact list.
99 @return an instance of MSNContactList (do not modify) if connected,
102 if self
.notificationFactory
:
103 return self
.notificationFactory
.contacts
107 def sendMessage(self
, userHandle
, text
, noerror
=False):
109 Sends a message to a contact. Can only be called after listSynchronized().
111 @param userHandle: the contact's MSN passport.
112 @param text: the text to send.
113 @param noerror: Set this to True if you don't want failed messages to bounce.
115 LogEvent(INFO
, self
.ident
)
116 if self
.notificationClient
:
117 self
._ensureSwitchboardSession
(userHandle
)
118 self
.switchboardSessions
[userHandle
].sendMessage(text
, noerror
)
120 self
.failedMessage(userHandle
, text
)
122 def sendAvatarRequest(self
, userHandle
):
124 Requests the avatar of a contact.
126 @param userHandle: the contact to request an avatar from.
127 @return: a Deferred() if the avatar can be fetched at this time.
128 This will fire with an argument of a tuple with the PNG
129 image data as the only element.
130 Otherwise returns None
133 LogEvent(INFO
, self
.ident
)
134 if not self
.notificationClient
: return
135 if MSNConnection
.GETALLAVATARS
:
136 self
._ensureSwitchboardSession
(userHandle
)
137 sb
= self
.switchboardSessions
.get(userHandle
)
138 if sb
: return sb
.sendAvatarRequest()
140 def sendFile(self
, userHandle
, filename
, filesize
):
142 Used to send a file to a contact.
144 @param username: the passport of the contact to send a file to.
145 @param filename: the name of the file to send.
146 @param filesize: the size of the file to send.
148 @return: A Deferred, which will fire with an argument of:
149 (fileSend, d) A FileSend object and a Deferred.
150 The new Deferred will pass one argument in a tuple,
151 whether or not the transfer is accepted. If you
152 receive a True, then you can call write() on the
153 fileSend object to send your file. Call close()
154 when the file is done.
155 NOTE: You MUST write() exactly as much as you
158 msnContact
= self
.getContacts().getContact(userHandle
)
160 raise ValueError, "Contact not found"
161 self
._ensureSwitchboardSession
(userHandle
)
162 return self
.switchboardSessions
[userHandle
].sendFile(msnContact
, filename
, filesize
)
164 def sendTypingToContact(self
, userHandle
):
166 Sends typing notification to a contact. Should send every 5secs.
167 @param userHandle: the contact to notify of our typing.
170 sb
= self
.switchboardSessions
.get(userHandle
)
171 if sb
: return sb
.sendTypingNotification()
173 def changeAvatar(self
, imageDataFunc
):
175 Changes the user's avatar.
176 @param imageDataFunc: a function which returns the new PNG avatar image data.
178 if self
.notificationClient
:
179 LogEvent(INFO
, self
.ident
)
180 self
.notificationClient
.changeAvatar(imageDataFunc
, push
=True)
181 # Save the avatar for reuse on disconnection
182 self
.savedEvents
.avatarImageDataFunc
= imageDataFunc
184 def changeStatus(self
, statusCode
, screenName
, personal
):
186 Changes your status details. All details must be given with
187 each call. This can be called before connection if you wish.
189 @param statusCode: the user's new status (look in msn.statusCodes).
190 @param screenName: the user's new screenName (up to 127 characters).
191 @param personal: the user's new personal message.
194 if not screenName
: screenName
= self
.username
195 if not statusCode
: statusCode
= msn
.STATUS_ONLINE
196 if not personal
: personal
= ""
197 if self
.notificationClient
:
198 changeCount
= [0] # Hack for Python's limited scope :(
199 def cb(ignored
=None):
201 if changeCount
[0] == 3:
202 self
.ourStatusChanged(statusCode
, screenName
, personal
)
203 def errcb(ignored
=None):
204 pass # FIXME, should we do something here?
205 LogEvent(INFO
, self
.ident
)
206 self
.notificationClient
.changeStatus(statusCode
.encode("utf-8")).addCallbacks(cb
, errcb
)
207 self
.notificationClient
.changeScreenName(screenName
.encode("utf-8")).addCallbacks(cb
, errcb
)
208 self
.notificationClient
.changePersonalMessage(personal
.encode("utf-8")).addCallbacks(cb
, errcb
)
209 # Remember the saved status
210 self
.savedEvents
.statusCode
= statusCode
211 self
.savedEvents
.screenName
= screenName
212 self
.savedEvents
.personal
= personal
214 def addContact(self
, listType
, userHandle
):
215 """ See msn.NotificationClient.addContact """
216 if self
.notificationClient
:
217 return self
.notificationClient
.addContact(listType
, str(userHandle
))
219 self
.savedEvents
.addContacts
.append((listType
, str(userHandle
)))
221 def remContact(self
, listType
, userHandle
):
222 """ See msn.NotificationClient.remContact """
223 if self
.notificationClient
:
224 return self
.notificationClient
.remContact(listType
, str(userHandle
))
226 self
.savedEvents
.remContacts
.append((listType
, str(userHandle
)))
229 """ Shuts down the whole connection. Don't try to call any
230 other methods after this one. Except maybe connect() """
231 if self
.notificationClient
:
232 self
.notificationClient
.logOut()
233 for c
in self
.connectors
:
236 if self
.notificationFactory
:
237 self
.notificationFactory
.stopTrying()
238 self
.notificationFactory
.msncon
= None
239 self
.notificationFactory
= None
240 for sbs
in self
.switchboardSessions
.values():
241 if hasattr(sbs
, "transport") and sbs
.transport
:
242 sbs
.transport
.loseConnection()
243 self
.switchboardSessions
= {}
245 self
.timeout
.cancel()
247 LogEvent(INFO
, self
.ident
)
251 def connectionFailed(self
, reason
=''):
252 """ Called when the connection to the server failed. """
254 def loginFailed(self
, reason
=''):
255 """ Called when the account could not be logged in. """
257 def connectionLost(self
, reason
=''):
258 """ Called when we are disconnected. """
260 def multipleLogin(self
):
261 """ Called when the server says there has been another login
262 for this account. """
264 def serverGoingDown(self
):
265 """ Called when the server says that it will be going down. """
267 def accountNotVerified(self
):
268 """ Called if this passport has not been verified. Certain
269 functions are not available. """
271 def userMapping(self
, passport
, jid
):
272 """ Called when it is brought to our attention that one of the
273 MSN contacts has a Jabber ID. You should communicate with Jabber. """
276 """ Called when we have authenticated, but before we receive
277 the contact list. """
279 def listSynchronized(self
):
280 """ Called when we have received the contact list. All methods
281 in this class are now valid. """
283 def ourStatusChanged(self
, statusCode
, screenName
, personal
):
284 """ Called when the user's status has changed. """
286 def gotMessage(self
, userHandle
, text
):
287 """ Called when a contact sends us a message """
289 def gotGroupchat(self
, msnGroupchat
, userHandle
):
290 """ Called when a conversation with more than one contact begins.
291 userHandle is the person who invited us.
292 The overriding method is expected to set msnGroupchat.groupchat to an object
293 that implements the following methods:
294 contactJoined(userHandle)
295 contactLeft(userHandle)
296 gotMessage(userHandle, text)
298 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
301 def gotContactTyping(self
, userHandle
):
302 """ Called when a contact sends typing notification.
303 Will be called once every 5 seconds. """
305 def failedMessage(self
, userHandle
, text
):
306 """ Called when a message we sent has been bounced back. """
308 def contactAvatarChanged(self
, userHandle
, hash):
309 """ Called when we receive a changed avatar hash for a contact.
310 You should call sendAvatarRequest(). """
312 def contactStatusChanged(self
, userHandle
):
313 """ Called when we receive status information for a contact. """
315 def gotFileReceive(self
, fileReceive
):
316 """ Called when a contact sends the user a file.
317 Call accept(fileHandle) or reject() on the object. """
319 def contactAddedMe(self
, userHandle
):
320 """ Called when a contact adds the user to their list. """
322 def contactRemovedMe(self
, userHandle
):
323 """ Called when a contact removes the user from their list. """
325 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
326 """ Received at login to tell about the user's Hotmail status """
328 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
329 """ Received in realtime whenever an email comes into the hotmail account """
331 def gotMSNAlert(self
, body
, action
, subscr
):
332 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
333 text of the alert. 'action' is a url for more information,
334 'subscr' is a url to modify your your alerts subscriptions. """
336 def gotAvatarImageData(self
, userHandle
, imageData
):
337 """ An contact's avatar has been received because a switchboard
338 session with them was started. """
346 self
.avatarImageDataFunc
= None
347 self
.addContacts
= []
348 self
.remContacts
= []
350 def send(self
, msncon
):
351 if self
.avatarImageDataFunc
:
352 msncon
.notificationClient
.changeAvatar(self
.avatarImageDataFunc
, push
=False)
353 if self
.screenName
or self
.statusCode
or self
.personal
:
354 msncon
.changeStatus(self
.statusCode
, self
.screenName
, self
.personal
)
355 for listType
, userHandle
in self
.addContacts
:
356 msncon
.addContact(listType
, userHandle
)
357 for listType
, userHandle
in self
.remContacts
:
358 msncon
.remContact(listType
, userHandle
)
362 class DispatchClient(msn
.DispatchClient
):
363 def gotNotificationReferral(self
, host
, port
):
365 self
.factory
.d
= None
366 if not d
or d
.called
:
367 return # Too slow! We've already timed out
368 d
.callback((host
, port
))
371 class NotificationClient(msn
.NotificationClient
):
372 def doDisconnect(self
, *args
):
373 if hasattr(self
, "transport") and self
.transport
:
374 self
.transport
.loseConnection()
376 def loginFailure(self
, message
):
377 self
.factory
.msncon
.loginFailed(message
)
379 def loggedIn(self
, userHandle
, verified
):
380 LogEvent(INFO
, self
.factory
.msncon
.ident
)
381 msn
.NotificationClient
.loggedIn(self
, userHandle
, verified
)
382 self
.factory
.msncon
._notificationClientReady
(self
)
383 self
.factory
.msncon
.loggedIn()
385 self
.factory
.msncon
.accountNotVerified()
388 msn
.NotificationClient
.logOut(self
)
389 # If we explicitly log out, then all of these events
391 self
.loginFailure
= self
.doDisconnect
392 self
.loggedIn
= self
.doDisconnect
393 self
.connectionLost
= lambda reason
: msn
.NotificationClient
.connectionLost(self
, reason
)
395 def connectionLost(self
, reason
):
396 if not self
.factory
.msncon
:
397 # If MSNConnection.logOut is called before _notificationClientReady
401 LogEvent(INFO
, self
.factory
.msncon
.ident
)
402 msn
.NotificationClient
.connectionLost(self
, reason
)
403 if self
.factory
.maxRetries
> self
.factory
.retries
:
404 self
.factory
.stopTrying()
405 self
.factory
.msncon
.connectionLost(reason
)
406 # Make sure this event is handled after any others
407 reactor
.callLater(0, wait
)
409 def gotMSNAlert(self
, body
, action
, subscr
):
410 LogEvent(INFO
, self
.factory
.msncon
.ident
)
411 self
.factory
.msncon
.gotMSNAlert(body
, action
, subscr
)
413 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
414 LogEvent(INFO
, self
.factory
.msncon
.ident
)
415 self
.factory
.msncon
.gotInitialEmailNotification(inboxunread
, foldersunread
)
417 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
418 LogEvent(INFO
, self
.factory
.msncon
.ident
)
419 self
.factory
.msncon
.gotRealtimeEmailNotification(mailfrom
, fromaddr
, subject
)
421 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
422 LogEvent(INFO
, self
.factory
.msncon
.ident
)
423 self
.factory
.msncon
.contactAddedMe(userHandle
)
425 def userRemovedMe(self
, userHandle
):
426 LogEvent(INFO
, self
.factory
.msncon
.ident
)
427 self
.factory
.msncon
.contactRemovedMe(userHandle
)
429 def listSynchronized(self
, *args
):
430 LogEvent(INFO
, self
.factory
.msncon
.ident
)
431 self
.factory
.msncon
._sendSavedEvents
()
432 self
.factory
.msncon
.listSynchronized()
434 def contactAvatarChanged(self
, userHandle
, hash):
435 LogEvent(INFO
, self
.factory
.msncon
.ident
)
436 self
.factory
.msncon
.contactAvatarChanged(userHandle
, hash)
438 def gotContactStatus(self
, userHandle
, statusCode
, screenName
):
439 LogEvent(INFO
, self
.factory
.msncon
.ident
)
440 self
.factory
.msncon
.contactStatusChanged(userHandle
)
442 def contactStatusChanged(self
, userHandle
, statusCode
, screenName
):
443 LogEvent(INFO
, self
.factory
.msncon
.ident
)
444 self
.factory
.msncon
.contactStatusChanged(userHandle
)
446 def contactPersonalChanged(self
, userHandle
, personal
):
447 LogEvent(INFO
, self
.factory
.msncon
.ident
)
448 self
.factory
.msncon
.contactStatusChanged(userHandle
)
450 def contactOffline(self
, userHandle
):
451 LogEvent(INFO
, self
.factory
.msncon
.ident
)
452 self
.factory
.msncon
.contactStatusChanged(userHandle
)
454 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
455 LogEvent(INFO
, self
.factory
.msncon
.ident
)
456 sb
= self
.factory
.msncon
.switchboardSessions
.get(userHandle
)
457 if sb
and sb
.transport
:
458 sb
.transport
.loseConnection()
459 sb
= OneSwitchboardSession(self
.factory
.msncon
, userHandle
)
460 self
.factory
.msncon
.switchboardSessions
[userHandle
] = sb
461 sb
.connectReply(host
, port
, key
, sessionID
)
463 def multipleLogin(self
):
464 LogEvent(INFO
, self
.factory
.msncon
.ident
)
465 self
.factory
.msncon
.multipleLogin()
467 def serverGoingDown(self
):
468 LogEvent(INFO
, self
.factory
.msncon
.ident
)
469 self
.factory
.msncon
.serverGoingDown()
473 class SwitchboardSessionBase(msn
.SwitchboardClient
):
474 def __init__(self
, msncon
):
475 msn
.SwitchboardClient
.__init
__(self
)
477 self
.msnobj
= msncon
.notificationClient
.msnobj
478 self
.userHandle
= msncon
.username
479 self
.ident
= (msncon
.ident
, "INVALID!!")
480 self
.messageBuffer
= []
484 def connectionLost(self
, reason
):
485 msn
.SwitchboardClient
.connectionLost(self
, reason
)
486 LogEvent(INFO
, self
.ident
)
490 self
.ident
= (self
.ident
[0], self
.ident
[1], "Disconnected!")
493 LogEvent(INFO
, self
.ident
)
498 LogEvent(INFO
, self
.ident
)
500 def sbRequestAccepted((host
, port
, key
)):
501 LogEvent(INFO
, self
.ident
)
504 factory
= ClientFactory()
505 factory
.buildProtocol
= lambda addr
: self
506 self
.msncon
.connectors
.append(reactor
.connectTCP(host
, port
, factory
, bindAddress
=(MSNConnection
.BINDADDRESS
, 0)))
507 def sbRequestFailed(ignored
=None):
508 LogEvent(INFO
, self
.ident
)
509 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
510 d
= self
.msncon
.notificationClient
.requestSwitchboardServer()
511 d
.addCallbacks(sbRequestAccepted
, sbRequestFailed
)
513 def connectReply(self
, host
, port
, key
, sessionID
):
514 LogEvent(INFO
, self
.ident
)
517 self
.sessionID
= sessionID
519 factory
= ClientFactory()
520 factory
.buildProtocol
= lambda addr
: self
521 self
.msncon
.connectors
.append(reactor
.connectTCP(host
, port
, factory
, bindAddress
=(MSNConnection
.BINDADDRESS
, 0)))
523 def flushBuffer(self
):
524 for message
, noerror
in self
.messageBuffer
[:]:
525 self
.messageBuffer
.remove((message
, noerror
))
526 self
.sendMessage(message
, noerror
)
527 for f
in self
.funcBuffer
[:]:
528 self
.funcBuffer
.remove(f
)
531 def failedMessage(self
, *ignored
):
532 raise NotImplementedError
534 def sendClientCaps(self
):
535 message
= msn
.MSNMessage()
536 message
.setHeader("Content-Type", "text/x-clientcaps")
537 message
.setHeader("Client-Name", "PyMSNt")
538 if hasattr(self
.msncon
, "jabberID"):
539 message
.setHeader("JabberID", str(self
.msncon
.jabberID
))
540 self
.sendMessage(message
)
542 def sendMessage(self
, message
, noerror
=False):
543 # Check to make sure that clientcaps only gets sent after
544 # the first text type message.
545 if isinstance(message
, msn
.MSNMessage
) and message
.getHeader("Content-Type").startswith("text"):
546 self
.sendMessage
= self
.sendMessageReal
547 self
.sendClientCaps()
548 return self
.sendMessage(message
, noerror
)
550 return self
.sendMessageReal(message
, noerror
)
552 def sendMessageReal(self
, text
, noerror
=False):
553 if not isinstance(text
, basestring
):
554 msn
.SwitchboardClient
.sendMessage(self
, text
)
557 self
.messageBuffer
.append((text
, noerror
))
559 LogEvent(INFO
, self
.ident
)
560 text
= str(text
.replace("\n", "\r\n").encode("utf-8"))
561 def failedMessage(ignored
):
563 self
.failedMessage(text
)
565 if len(text
) < MSNConnection
.MAXMESSAGESIZE
:
566 message
= msn
.MSNMessage(message
=text
)
567 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
569 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
571 d
.addCallbacks(failedMessage
, failedMessage
)
574 chunks
= int(math
.ceil(len(text
) / float(MSNConnection
.MAXMESSAGESIZE
)))
576 guid
= msn
.random_guid()
577 while chunk
< chunks
:
578 offset
= chunk
* MSNConnection
.MAXMESSAGESIZE
579 message
= msn
.MSNMessage(message
=text
[offset
: offset
+ MSNConnection
.MAXMESSAGESIZE
])
580 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
581 message
.setHeader("Message-ID", guid
)
583 message
.setHeader("Chunks", str(chunks
))
585 message
.delHeader("MIME-Version")
586 message
.delHeader("Content-Type")
587 message
.setHeader("Chunk", str(chunk
))
589 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
591 d
.addCallbacks(failedMessage
, failedMessage
)
596 class MultiSwitchboardSession(SwitchboardSessionBase
):
597 """ Create one of me to chat to multiple contacts """
599 def __init__(self
, msncon
):
600 """ Automatically creates a new switchboard connection to the server """
601 SwitchboardSessionBase
.__init
__(self
, msncon
)
602 self
.ident
= (self
.msncon
.ident
, repr(self
))
603 self
.contactCount
= 0
604 self
.groupchat
= None
607 def failedMessage(self
, text
):
608 self
.groupchat
.gotMessage("BOUNCE", text
)
610 def sendMessage(self
, text
, noerror
=False):
611 """ Used to send a mesage to the groupchat. Can be called immediately
612 after instantiation. """
613 if self
.contactCount
> 0:
614 SwitchboardSessionBase
.sendMessage(self
, text
, noerror
)
616 #self.messageBuffer.append((message, noerror))
617 pass # They're sending messages to an empty room. Ignore.
619 def inviteUser(self
, userHandle
):
620 """ Used to invite a contact to the groupchat. Can be called immediately
621 after instantiation. """
622 userHandle
= str(userHandle
)
624 LogEvent(INFO
, self
.ident
, "immediate")
625 msn
.SwitchboardClient
.inviteUser(self
, userHandle
)
627 LogEvent(INFO
, self
.ident
, "pending")
628 self
.funcBuffer
.append(lambda: msn
.SwitchboardClient
.inviteUser(self
, userHandle
))
630 def gotMessage(self
, message
):
631 self
.groupchat
.gotMessage(message
.userHandle
, message
.getMessage())
633 def userJoined(self
, userHandle
, screenName
=''):
634 LogEvent(INFO
, self
.ident
)
635 self
.contactCount
+= 1
636 self
.groupchat
.contactJoined(userHandle
)
638 def userLeft(self
, userHandle
):
639 LogEvent(INFO
, self
.ident
)
640 self
.contactCount
-= 1
641 self
.groupchat
.contactLeft(userHandle
)
645 class OneSwitchboardSession(SwitchboardSessionBase
):
646 def __init__(self
, msncon
, remoteUser
):
647 SwitchboardSessionBase
.__init
__(self
, msncon
)
648 self
.remoteUser
= str(remoteUser
)
649 self
.ident
= (self
.msncon
.ident
, self
.remoteUser
)
650 self
.chattingUsers
= []
653 def connectionLost(self
, reason
):
655 self
.timeout
.cancel()
657 for message
, noerror
in self
.messageBuffer
:
659 self
.failedMessage(message
)
660 self
.messageBuffer
= []
662 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
663 # Unexpected disconnection. Must remove us from msncon
664 self
.msncon
.switchboardSessions
.pop(self
.remoteUser
)
666 SwitchboardSessionBase
.connectionLost(self
, reason
)
669 LogEvent(INFO
, self
.ident
)
671 for user
in self
.chattingUsers
:
672 self
.userJoined(user
)
674 self
.timeout
.cancel()
678 def _switchToMulti(self
, userHandle
):
679 LogEvent(INFO
, self
.ident
)
680 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
681 self
.__class
__ = MultiSwitchboardSession
683 self
.contactCount
= 0
684 self
.msncon
.gotGroupchat(self
, userHandle
)
685 assert self
.groupchat
687 def failedMessage(self
, text
):
688 self
.msncon
.failedMessage(self
.remoteUser
, text
)
692 LogEvent(INFO
, self
.ident
)
694 def failCB(arg
=None):
696 self
.transport
.loseConnection()
697 if not (self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
)):
699 LogEvent(INFO
, self
.ident
, "User has not joined after 30 seconds.")
700 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
701 d
= self
.inviteUser(self
.remoteUser
)
703 self
.timeout
= reactor
.callLater(30.0, failCB
)
707 def gotChattingUsers(self
, users
):
708 for userHandle
in users
.keys():
709 self
.chattingUsers
.append(userHandle
)
711 def userJoined(self
, userHandle
, screenName
=''):
712 LogEvent(INFO
, self
.ident
)
713 if not self
.reply
and not self
.ready
:
715 if userHandle
!= self
.remoteUser
:
716 # Another user has joined, so we now have three participants.
717 remoteUser
= self
.remoteUser
718 self
._switchToMulti
(remoteUser
)
719 self
.userJoined(remoteUser
)
720 self
.userJoined(userHandle
)
722 def updateAvatarCB((imageData
, )):
724 self
.msncon
.gotAvatarImageData(self
.remoteUser
, imageData
)
725 d
= self
.sendAvatarRequest()
727 d
.addCallback(updateAvatarCB
)
729 def userLeft(self
, userHandle
):
731 if userHandle
== self
.remoteUser
:
732 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
733 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
734 reactor
.callLater(0, wait
) # Make sure this is handled after everything else
736 def gotMessage(self
, message
):
737 LogEvent(INFO
, self
.ident
)
738 cTypes
= [s
.strip() for s
in message
.getHeader("Content-Type").split(';')]
739 if "text/plain" == cTypes
[0]:
741 if len(cTypes
) > 1 and cTypes
[1].lower().find("utf-8") >= 0:
742 text
= message
.getMessage().decode("utf-8")
744 text
= message
.getMessage()
745 self
.msncon
.gotMessage(self
.remoteUser
, text
)
746 except UnicodeDecodeError:
747 LogEvent(WARN
, self
.ident
, "Message lost!")
748 self
.msncon
.gotMessage(self
.remoteUser
, "A message was lost.")
750 elif "text/x-clientcaps" == cTypes
[0]:
751 if message
.hasHeader("JabberID"):
752 jid
= message
.getHeader("JabberID")
753 self
.msncon
.userMapping(message
.userHandle
, jid
)
755 LogEvent(INFO
, self
.ident
, "Discarding unknown message type.")
757 def gotFileReceive(self
, fileReceive
):
758 LogEvent(INFO
, self
.ident
)
759 self
.msncon
.gotFileReceive(fileReceive
)
761 def gotContactTyping(self
, message
):
762 LogEvent(INFO
, self
.ident
)
763 self
.msncon
.gotContactTyping(message
.userHandle
)
765 def sendTypingNotification(self
):
766 LogEvent(INFO
, self
.ident
)
768 msn
.SwitchboardClient
.sendTypingNotification(self
)
770 CAPS
= msn
.MSNContact
.MSNC1 | msn
.MSNContact
.MSNC2 | msn
.MSNContact
.MSNC3 | msn
.MSNContact
.MSNC4
771 def sendAvatarRequest(self
):
772 if not self
.ready
: return
773 msnContacts
= self
.msncon
.getContacts()
774 if not msnContacts
: return
775 msnContact
= msnContacts
.getContact(self
.remoteUser
)
776 if not (msnContact
and msnContact
.caps
& self
.CAPS
and msnContact
.msnobj
): return
777 if msnContact
.msnobjGot
: return
778 msnContact
.msnobjGot
= True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
779 return msn
.SwitchboardClient
.sendAvatarRequest(self
, msnContact
)
781 def sendFile(self
, msnContact
, filename
, filesize
):
782 def doSendFile(ignored
=None):
783 d
.callback(msn
.SwitchboardClient
.sendFile(self
, msnContact
, filename
, filesize
))
786 reactor
.callLater(0, doSendFile
)
788 self
.funcBuffer
.append(doSendFile
)