]>
code.delx.au - pymsnt/blob - src/tlib/msn/msnw.py
214ecaec987b360966dc63c8b1b0b31e7bdc93f9
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
.stopTrying()
233 self
.notificationFactory
.msncon
= None
235 for sbs
in self
.switchboardSessions
.values():
236 if hasattr(sbs
, "transport") and sbs
.transport
:
237 sbs
.transport
.loseConnection()
238 self
.switchboardSessions
= {}
240 self
.timeout
.cancel()
242 LogEvent(INFO
, self
.ident
)
246 def connectionFailed(self
, reason
=''):
247 """ Called when the connection to the server failed. """
249 def loginFailed(self
, reason
=''):
250 """ Called when the account could not be logged in. """
252 def connectionLost(self
, reason
=''):
253 """ Called when we are disconnected. """
255 def multipleLogin(self
):
256 """ Called when the server says there has been another login
257 for this account. """
259 def serverGoingDown(self
):
260 """ Called when the server says that it will be going down. """
262 def accountNotVerified(self
):
263 """ Called if this passport has not been verified. Certain
264 functions are not available. """
266 def userMapping(self
, passport
, jid
):
267 """ Called when it is brought to our attention that one of the
268 MSN contacts has a Jabber ID. You should communicate with Jabber. """
271 """ Called when we have authenticated, but before we receive
272 the contact list. """
274 def listSynchronized(self
):
275 """ Called when we have received the contact list. All methods
276 in this class are now valid. """
278 def ourStatusChanged(self
, statusCode
, screenName
, personal
):
279 """ Called when the user's status has changed. """
281 def gotMessage(self
, userHandle
, text
):
282 """ Called when a contact sends us a message """
284 def gotGroupchat(self
, msnGroupchat
, userHandle
):
285 """ Called when a conversation with more than one contact begins.
286 userHandle is the person who invited us.
287 The overriding method is expected to set msnGroupchat.groupchat to an object
288 that implements the following methods:
289 contactJoined(userHandle)
290 contactLeft(userHandle)
291 gotMessage(userHandle, text)
293 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
296 def gotContactTyping(self
, userHandle
):
297 """ Called when a contact sends typing notification.
298 Will be called once every 5 seconds. """
300 def failedMessage(self
, userHandle
, text
):
301 """ Called when a message we sent has been bounced back. """
303 def contactAvatarChanged(self
, userHandle
, hash):
304 """ Called when we receive a changed avatar hash for a contact.
305 You should call sendAvatarRequest(). """
307 def contactStatusChanged(self
, userHandle
):
308 """ Called when we receive status information for a contact. """
310 def gotFileReceive(self
, fileReceive
):
311 """ Called when a contact sends the user a file.
312 Call accept(fileHandle) or reject() on the object. """
314 def contactAddedMe(self
, userHandle
):
315 """ Called when a contact adds the user to their list. """
317 def contactRemovedMe(self
, userHandle
):
318 """ Called when a contact removes the user from their list. """
320 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
321 """ Received at login to tell about the user's Hotmail status """
323 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
324 """ Received in realtime whenever an email comes into the hotmail account """
326 def gotMSNAlert(self
, body
, action
, subscr
):
327 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
328 text of the alert. 'action' is a url for more information,
329 'subscr' is a url to modify your your alerts subscriptions. """
331 def gotAvatarImageData(self
, userHandle
, imageData
):
332 """ An contact's avatar has been received because a switchboard
333 session with them was started. """
341 self
.avatarImageData
= ""
342 self
.addContacts
= []
343 self
.remContacts
= []
345 def send(self
, msncon
):
346 if self
.avatarImageData
:
347 msncon
.notificationClient
.changeAvatar(self
.avatarImageData
, push
=False)
348 if self
.screenName
or self
.statusCode
or self
.personal
:
349 msncon
.changeStatus(self
.statusCode
, self
.screenName
, self
.personal
)
350 for listType
, userHandle
in self
.addContacts
:
351 msncon
.addContact(listType
, userHandle
)
352 for listType
, userHandle
in self
.remContacts
:
353 msncon
.remContact(listType
, userHandle
)
357 class DispatchClient(msn
.DispatchClient
):
358 def gotNotificationReferral(self
, host
, port
):
359 if self
.factory
.d
.called
: return # Too slow! We've already timed out
360 self
.factory
.d
.callback((host
, port
))
363 class NotificationClient(msn
.NotificationClient
):
364 def doDisconnect(self
, *args
):
365 if hasattr(self
, "transport") and self
.transport
:
366 self
.transport
.loseConnection()
368 def loginFailure(self
, message
):
369 self
.factory
.msncon
.loginFailed(message
)
371 def loggedIn(self
, userHandle
, verified
):
372 LogEvent(INFO
, self
.factory
.msncon
.ident
)
373 msn
.NotificationClient
.loggedIn(self
, userHandle
, verified
)
374 self
.factory
.msncon
._notificationClientReady
(self
)
375 self
.factory
.msncon
.loggedIn()
377 self
.factory
.msncon
.accountNotVerified()
380 msn
.NotificationClient
.logOut(self
)
381 # If we explicitly log out, then all of these events
383 self
.loginFailure
= self
.doDisconnect
384 self
.loggedIn
= self
.doDisconnect
385 self
.connectionLost
= self
.doDisconnect
387 def connectionLost(self
, reason
):
388 if not self
.factory
.msncon
:
389 # If MSNConnection.logOut is called before _notificationClientReady
393 LogEvent(INFO
, self
.factory
.msncon
.ident
)
394 msn
.NotificationClient
.connectionLost(self
, reason
)
395 if self
.factory
.maxRetries
> self
.factory
.retries
:
396 self
.factory
.stopTrying()
397 self
.factory
.msncon
.connectionLost(reason
)
398 # Make sure this event is handled after any others
399 reactor
.callLater(0, wait
)
401 def gotMSNAlert(self
, body
, action
, subscr
):
402 LogEvent(INFO
, self
.factory
.msncon
.ident
)
403 self
.factory
.msncon
.gotMSNAlert(body
, action
, subscr
)
405 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
406 LogEvent(INFO
, self
.factory
.msncon
.ident
)
407 self
.factory
.msncon
.gotInitialEmailNotification(inboxunread
, foldersunread
)
409 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
410 LogEvent(INFO
, self
.factory
.msncon
.ident
)
411 self
.factory
.msncon
.gotRealtimeEmailNotification(mailfrom
, fromaddr
, subject
)
413 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
414 LogEvent(INFO
, self
.factory
.msncon
.ident
)
415 self
.factory
.msncon
.contactAddedMe(userHandle
)
417 def userRemovedMe(self
, userHandle
):
418 LogEvent(INFO
, self
.factory
.msncon
.ident
)
419 self
.factory
.msncon
.contactRemovedMe(userHandle
)
421 def listSynchronized(self
, *args
):
422 LogEvent(INFO
, self
.factory
.msncon
.ident
)
423 self
.factory
.msncon
._sendSavedEvents
()
424 self
.factory
.msncon
.listSynchronized()
426 def contactAvatarChanged(self
, userHandle
, hash):
427 LogEvent(INFO
, self
.factory
.msncon
.ident
)
428 self
.factory
.msncon
.contactAvatarChanged(userHandle
, hash)
430 def gotContactStatus(self
, userHandle
, statusCode
, screenName
):
431 LogEvent(INFO
, self
.factory
.msncon
.ident
)
432 self
.factory
.msncon
.contactStatusChanged(userHandle
)
434 def contactStatusChanged(self
, userHandle
, statusCode
, screenName
):
435 LogEvent(INFO
, self
.factory
.msncon
.ident
)
436 self
.factory
.msncon
.contactStatusChanged(userHandle
)
438 def contactPersonalChanged(self
, userHandle
, personal
):
439 LogEvent(INFO
, self
.factory
.msncon
.ident
)
440 self
.factory
.msncon
.contactStatusChanged(userHandle
)
442 def contactOffline(self
, userHandle
):
443 LogEvent(INFO
, self
.factory
.msncon
.ident
)
444 self
.factory
.msncon
.contactStatusChanged(userHandle
)
446 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
447 LogEvent(INFO
, self
.factory
.msncon
.ident
)
448 sb
= self
.factory
.msncon
.switchboardSessions
.get(userHandle
)
449 if sb
and sb
.transport
:
450 sb
.transport
.loseConnection()
451 sb
= OneSwitchboardSession(self
.factory
.msncon
, userHandle
)
452 self
.factory
.msncon
.switchboardSessions
[userHandle
] = sb
453 sb
.connectReply(host
, port
, key
, sessionID
)
455 def multipleLogin(self
):
456 LogEvent(INFO
, self
.factory
.msncon
.ident
)
457 self
.factory
.msncon
.multipleLogin()
459 def serverGoingDown(self
):
460 LogEvent(INFO
, self
.factory
.msncon
.ident
)
461 self
.factory
.msncon
.serverGoingDown()
465 class SwitchboardSessionBase(msn
.SwitchboardClient
):
466 def __init__(self
, msncon
):
467 msn
.SwitchboardClient
.__init
__(self
)
469 self
.msnobj
= msncon
.notificationClient
.msnobj
470 self
.userHandle
= msncon
.username
471 self
.ident
= (msncon
.ident
, "INVALID!!")
472 self
.messageBuffer
= []
476 def connectionLost(self
, reason
):
477 msn
.SwitchboardClient
.connectionLost(self
, reason
)
478 LogEvent(INFO
, self
.ident
)
482 self
.ident
= (self
.ident
[0], self
.ident
[1], "Disconnected!")
485 LogEvent(INFO
, self
.ident
)
490 LogEvent(INFO
, self
.ident
)
492 def sbRequestAccepted((host
, port
, key
)):
493 LogEvent(INFO
, self
.ident
)
496 factory
= ClientFactory()
497 factory
.buildProtocol
= lambda addr
: self
498 reactor
.connectTCP(host
, port
, factory
)
499 def sbRequestFailed(ignored
=None):
500 LogEvent(INFO
, self
.ident
)
501 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
502 d
= self
.msncon
.notificationClient
.requestSwitchboardServer()
503 d
.addCallbacks(sbRequestAccepted
, sbRequestFailed
)
505 def connectReply(self
, host
, port
, key
, sessionID
):
506 LogEvent(INFO
, self
.ident
)
509 self
.sessionID
= sessionID
511 factory
= ClientFactory()
512 factory
.buildProtocol
= lambda addr
: self
513 reactor
.connectTCP(host
, port
, factory
)
515 def flushBuffer(self
):
516 for message
, noerror
in self
.messageBuffer
[:]:
517 self
.messageBuffer
.remove((message
, noerror
))
518 self
.sendMessage(message
, noerror
)
519 for f
in self
.funcBuffer
[:]:
520 self
.funcBuffer
.remove(f
)
523 def failedMessage(self
, *ignored
):
524 raise NotImplementedError
526 def sendClientCaps(self
):
527 message
= msn
.MSNMessage()
528 message
.setHeader("Content-Type", "text/x-clientcaps")
529 message
.setHeader("Client-Name", "PyMSNt")
530 if hasattr(self
.msncon
, "jabberID"):
531 message
.setHeader("JabberID", str(self
.msncon
.jabberID
))
532 self
.sendMessage(message
)
534 def sendMessage(self
, message
, noerror
=False):
535 # Check to make sure that clientcaps only gets sent after
536 # the first text type message.
537 if isinstance(message
, msn
.MSNMessage
) and message
.getHeader("Content-Type").startswith("text"):
538 self
.sendMessage
= self
.sendMessageReal
539 self
.sendClientCaps()
540 return self
.sendMessage(message
, noerror
)
542 return self
.sendMessageReal(message
, noerror
)
544 def sendMessageReal(self
, text
, noerror
=False):
545 if not isinstance(text
, basestring
):
546 msn
.SwitchboardClient
.sendMessage(self
, text
)
549 self
.messageBuffer
.append((text
, noerror
))
551 LogEvent(INFO
, self
.ident
)
552 text
= str(text
.replace("\n", "\r\n").encode("utf-8"))
553 def failedMessage(ignored
):
555 self
.failedMessage(text
)
557 if len(text
) < MSNConnection
.MAXMESSAGESIZE
:
558 message
= msn
.MSNMessage(message
=text
)
559 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
561 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
563 d
.addCallback(failedMessage
)
566 chunks
= int(math
.ceil(len(text
) / float(MSNConnection
.MAXMESSAGESIZE
)))
568 guid
= msn
.random_guid()
569 while chunk
< chunks
:
570 offset
= chunk
* MSNConnection
.MAXMESSAGESIZE
571 message
= msn
.MSNMessage(message
=text
[offset
: offset
+ MSNConnection
.MAXMESSAGESIZE
])
572 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
573 message
.setHeader("Message-ID", guid
)
575 message
.setHeader("Chunks", str(chunks
))
577 message
.delHeader("MIME-Version")
578 message
.delHeader("Content-Type")
579 message
.setHeader("Chunk", str(chunk
))
581 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
583 d
.addCallback(failedMessage
)
588 class MultiSwitchboardSession(SwitchboardSessionBase
):
589 """ Create one of me to chat to multiple contacts """
591 def __init__(self
, msncon
):
592 """ Automatically creates a new switchboard connection to the server """
593 SwitchboardSessionBase
.__init
__(self
, msncon
)
594 self
.ident
= (self
.msncon
.ident
, repr(self
))
595 self
.contactCount
= 0
596 self
.groupchat
= None
599 def failedMessage(self
, text
):
600 self
.groupchat
.gotMessage("BOUNCE", text
)
602 def sendMessage(self
, text
, noerror
=False):
603 """ Used to send a mesage to the groupchat. Can be called immediately
604 after instantiation. """
605 if self
.contactCount
> 0:
606 SwitchboardSessionBase
.sendMessage(self
, text
, noerror
)
608 #self.messageBuffer.append((message, noerror))
609 pass # They're sending messages to an empty room. Ignore.
611 def inviteUser(self
, userHandle
):
612 """ Used to invite a contact to the groupchat. Can be called immediately
613 after instantiation. """
614 userHandle
= str(userHandle
)
616 LogEvent(INFO
, self
.ident
, "immediate")
617 msn
.SwitchboardClient
.inviteUser(self
, userHandle
)
619 LogEvent(INFO
, self
.ident
, "pending")
620 self
.funcBuffer
.append(lambda: msn
.SwitchboardClient
.inviteUser(self
, userHandle
))
622 def gotMessage(self
, message
):
623 self
.groupchat
.gotMessage(message
.userHandle
, message
.getMessage())
625 def userJoined(self
, userHandle
, screenName
=''):
626 LogEvent(INFO
, self
.ident
)
627 self
.contactCount
+= 1
628 self
.groupchat
.contactJoined(userHandle
)
630 def userLeft(self
, userHandle
):
631 LogEvent(INFO
, self
.ident
)
632 self
.contactCount
-= 1
633 self
.groupchat
.contactLeft(userHandle
)
637 class OneSwitchboardSession(SwitchboardSessionBase
):
638 def __init__(self
, msncon
, remoteUser
):
639 SwitchboardSessionBase
.__init
__(self
, msncon
)
640 self
.remoteUser
= str(remoteUser
)
641 self
.ident
= (self
.msncon
.ident
, self
.remoteUser
)
642 self
.chattingUsers
= []
645 def connectionLost(self
, reason
):
647 self
.timeout
.cancel()
649 for message
, noerror
in self
.messageBuffer
:
651 self
.failedMessage(message
)
652 self
.messageBuffer
= []
653 SwitchboardSessionBase
.connectionLost(self
, reason
)
656 LogEvent(INFO
, self
.ident
)
658 for user
in self
.chattingUsers
:
659 self
.userJoined(user
)
661 self
.timeout
.cancel()
665 def _switchToMulti(self
, userHandle
):
666 LogEvent(INFO
, self
.ident
)
667 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
668 self
.__class
__ = MultiSwitchboardSession
670 self
.contactCount
= 0
671 self
.msncon
.gotGroupchat(self
, userHandle
)
672 assert self
.groupchat
674 def failedMessage(self
, text
):
675 self
.msncon
.failedMessage(self
.remoteUser
, text
)
679 LogEvent(INFO
, self
.ident
)
681 def failCB(arg
=None):
682 if not (self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
)):
684 LogEvent(INFO
, self
.ident
, "User has not joined after 30 seconds.")
685 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
687 self
.transport
.loseConnection()
688 d
= self
.inviteUser(self
.remoteUser
)
690 self
.timeout
= reactor
.callLater(30.0, failCB
)
694 def gotChattingUsers(self
, users
):
695 for userHandle
in users
.keys():
696 self
.chattingUsers
.append(userHandle
)
698 def userJoined(self
, userHandle
, screenName
=''):
699 LogEvent(INFO
, self
.ident
)
702 if userHandle
!= self
.remoteUser
:
703 # Another user has joined, so we now have three participants.
704 remoteUser
= self
.remoteUser
705 self
._switchToMulti
(remoteUser
)
706 self
.userJoined(remoteUser
)
707 self
.userJoined(userHandle
)
709 def updateAvatarCB((imageData
, )):
711 self
.msncon
.gotAvatarImageData(self
.remoteUser
, imageData
)
712 d
= self
.sendAvatarRequest()
714 d
.addCallback(updateAvatarCB
)
716 def userLeft(self
, userHandle
):
718 if userHandle
== self
.remoteUser
:
719 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
720 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
721 reactor
.callLater(0, wait
) # Make sure this is handled after everything else
723 def gotMessage(self
, message
):
724 LogEvent(INFO
, self
.ident
)
725 cTypes
= [s
.strip() for s
in message
.getHeader("Content-Type").split(';')]
726 if "text/plain" == cTypes
[0]:
728 if len(cTypes
) > 1 and cTypes
[1].lower().find("utf-8") >= 0:
729 text
= message
.getMessage().decode("utf-8")
731 text
= message
.getMessage()
732 self
.msncon
.gotMessage(self
.remoteUser
, text
)
734 self
.msncon
.gotMessage(self
.remoteUser
, "A message was lost.")
736 elif "text/x-clientcaps" == cTypes
[0]:
737 if message
.hasHeader("JabberID"):
738 jid
= message
.getHeader("JabberID")
739 self
.msncon
.userMapping(message
.userHandle
, jid
)
741 LogEvent(INFO
, self
.ident
, "Discarding unknown message type.")
743 def gotFileReceive(self
, fileReceive
):
744 LogEvent(INFO
, self
.ident
)
745 self
.msncon
.gotFileReceive(fileReceive
)
747 def gotContactTyping(self
, message
):
748 LogEvent(INFO
, self
.ident
)
749 self
.msncon
.gotContactTyping(message
.userHandle
)
751 def sendTypingNotification(self
):
752 LogEvent(INFO
, self
.ident
)
754 msn
.SwitchboardClient
.sendTypingNotification(self
)
756 CAPS
= msn
.MSNContact
.MSNC1 | msn
.MSNContact
.MSNC2 | msn
.MSNContact
.MSNC3 | msn
.MSNContact
.MSNC4
757 def sendAvatarRequest(self
):
758 if not self
.ready
: return
759 msnContacts
= self
.msncon
.getContacts()
760 if not msnContacts
: return
761 msnContact
= msnContacts
.getContact(self
.remoteUser
)
762 if not (msnContact
and msnContact
.caps
& self
.CAPS
and msnContact
.msnobj
): return
763 if msnContact
.msnobjGot
: return
764 msnContact
.msnobjGot
= True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
765 return msn
.SwitchboardClient
.sendAvatarRequest(self
, msnContact
)
767 def sendFile(self
, msnContact
, filename
, filesize
):
768 def doSendFile(ignored
=None):
769 d
.callback(msn
.SwitchboardClient
.sendFile(self
, msnContact
, filename
, filesize
))
772 reactor
.callLater(0, doSendFile
)
774 self
.funcBuffer
.append(doSendFile
)