]>
code.delx.au - pymsnt/blob - src/tlib/msn/msnw.py
1 # Copyright 2004-2005 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
5 from twisted
.internet
import reactor
6 from twisted
.internet
.defer
import Deferred
7 from twisted
.internet
.protocol
import ClientFactory
10 import math
, base64
, binascii
13 from debug
import LogEvent
, INFO
, WARN
, ERROR
14 from tlib
.msn
import msn
19 All interaction should be with the MSNConnection and MultiSwitchboardSession classes.
20 You should not directly instantiate any objects of other classes.
24 """ Manages all the Twisted factories, etc """
26 SWITCHBOARDTIMEOUT
= 30.0*60.0
29 def __init__(self
, username
, password
, ident
):
30 """ Connects to the MSN servers.
31 @param username: the MSN passport to connect with.
32 @param password: the password for this account.
33 @param ident: a unique identifier to use in logging.
35 self
.username
= username
36 self
.password
= password
39 self
.notificationFactory
= None
40 self
.notificationClient
= None
42 LogEvent(INFO
, self
.ident
)
45 """ Automatically called by the constructor """
47 self
.switchboardSessions
= {}
48 self
.savedEvents
= SavedEvents() # Save any events that occur before connect
49 self
._getNotificationReferral
()
51 def _getNotificationReferral(self
):
55 d
.errback(Exception("Timeout"))
56 self
.logOut() # Clean up everything
57 self
.timeout
= reactor
.callLater(30, timeout
)
58 dispatchFactory
= msn
.DispatchFactory()
59 dispatchFactory
.userHandle
= self
.username
60 dispatchFactory
.protocol
= DispatchClient
63 d
.addCallbacks(self
._gotNotificationReferral
, self
.connectionFailed
)
64 self
.connectors
.append(reactor
.connectTCP("messenger.hotmail.com", 1863, dispatchFactory
))
65 LogEvent(INFO
, self
.ident
)
67 def _gotNotificationReferral(self
, (host
, port
)):
70 # Create the NotificationClient
71 self
.notificationFactory
= msn
.NotificationFactory()
72 self
.notificationFactory
.userHandle
= self
.username
73 self
.notificationFactory
.password
= self
.password
74 self
.notificationFactory
.msncon
= self
75 self
.notificationFactory
.protocol
= NotificationClient
76 self
.connectors
.append(reactor
.connectTCP(host
, port
, self
.notificationFactory
))
77 LogEvent(INFO
, self
.ident
)
79 def _sendSavedEvents(self
):
80 self
.savedEvents
.send(self
)
82 def _notificationClientReady(self
, notificationClient
):
83 self
.notificationClient
= notificationClient
85 def _ensureSwitchboardSession(self
, userHandle
):
86 if not self
.switchboardSessions
.has_key(userHandle
):
87 sb
= OneSwitchboardSession(self
, userHandle
)
89 self
.switchboardSessions
[userHandle
] = sb
94 def getContacts(self
):
95 """ Gets the contact list.
97 @return an instance of MSNContactList (do not modify) if connected,
100 if self
.notificationFactory
:
101 return self
.notificationFactory
.contacts
105 def sendMessage(self
, userHandle
, text
, noerror
=False):
107 Sends a message to a contact. Can only be called after listSynchronized().
109 @param userHandle: the contact's MSN passport.
110 @param text: the text to send.
111 @param noerror: Set this to True if you don't want failed messages to bounce.
113 LogEvent(INFO
, self
.ident
)
114 if self
.notificationClient
:
115 self
._ensureSwitchboardSession
(userHandle
)
116 self
.switchboardSessions
[userHandle
].sendMessage(text
, noerror
)
118 self
.failedMessage(userHandle
, text
)
120 def sendAvatarRequest(self
, userHandle
):
122 Requests the avatar of a contact.
124 @param userHandle: the contact to request an avatar from.
125 @return: a Deferred() if the avatar can be fetched at this time.
126 This will fire with an argument of a tuple with the PNG
127 image data as the only element.
128 Otherwise returns None
131 LogEvent(INFO
, self
.ident
)
132 if not self
.notificationClient
: return
133 if MSNConnection
.GETALLAVATARS
:
134 self
._ensureSwitchboardSession
(userHandle
)
135 sb
= self
.switchboardSessions
.get(userHandle
)
136 if sb
: return sb
.sendAvatarRequest()
138 def sendFile(self
, userHandle
, filename
, filesize
):
140 Used to send a file to a contact.
142 @param username: the passport of the contact to send a file to.
143 @param filename: the name of the file to send.
144 @param filesize: the size of the file to send.
146 @return: A Deferred, which will fire with an argument of:
147 (fileSend, d) A FileSend object and a Deferred.
148 The new Deferred will pass one argument in a tuple,
149 whether or not the transfer is accepted. If you
150 receive a True, then you can call write() on the
151 fileSend object to send your file. Call close()
152 when the file is done.
153 NOTE: You MUST write() exactly as much as you
156 msnContact
= self
.getContacts().getContact(userHandle
)
158 raise ValueError, "Contact not found"
159 self
._ensureSwitchboardSession
(userHandle
)
160 return self
.switchboardSessions
[userHandle
].sendFile(msnContact
, filename
, filesize
)
162 def sendTypingToContact(self
, userHandle
):
164 Sends typing notification to a contact. Should send every 5secs.
165 @param userHandle: the contact to notify of our typing.
168 sb
= self
.switchboardSessions
.get(userHandle
)
169 if sb
: return sb
.sendTypingNotification()
171 def changeAvatar(self
, imageData
):
173 Changes the user's avatar.
174 @param imageData: the new PNG avatar image data.
176 if self
.notificationClient
:
177 LogEvent(INFO
, self
.ident
)
178 self
.notificationClient
.changeAvatar(imageData
, push
=True)
179 # Save the avatar for reuse on disconnection
180 self
.savedEvents
.avatarImageData
= imageData
182 def changeStatus(self
, statusCode
, screenName
, personal
):
184 Changes your status details. All details must be given with
185 each call. This can be called before connection if you wish.
187 @param statusCode: the user's new status (look in msn.statusCodes).
188 @param screenName: the user's new screenName (up to 127 characters).
189 @param personal: the user's new personal message.
192 if not screenName
: screenName
= self
.username
193 if not statusCode
: statusCode
= msn
.STATUS_ONLINE
194 if not personal
: personal
= ""
195 if self
.notificationClient
:
196 changeCount
= [0] # Hack for Python's limited scope :(
197 def cb(ignored
=None):
199 if changeCount
[0] == 3:
200 self
.ourStatusChanged(statusCode
, screenName
, personal
)
201 def errcb(ignored
=None):
202 pass # FIXME, should we do something here?
203 LogEvent(INFO
, self
.ident
)
204 self
.notificationClient
.changeStatus(statusCode
.encode("utf-8")).addCallbacks(cb
, errcb
)
205 self
.notificationClient
.changeScreenName(screenName
.encode("utf-8")).addCallbacks(cb
, errcb
)
206 self
.notificationClient
.changePersonalMessage(personal
.encode("utf-8")).addCallbacks(cb
, errcb
)
207 # Remember the saved status
208 self
.savedEvents
.statusCode
= statusCode
209 self
.savedEvents
.screenName
= screenName
210 self
.savedEvents
.personal
= personal
212 def addContact(self
, listType
, userHandle
):
213 """ See msn.NotificationClient.addContact """
214 if self
.notificationClient
:
215 return self
.notificationClient
.addContact(listType
, str(userHandle
))
217 self
.savedEvents
.addContacts
.append((listType
, str(userHandle
)))
219 def remContact(self
, listType
, userHandle
):
220 """ See msn.NotificationClient.remContact """
221 if self
.notificationClient
:
222 return self
.notificationClient
.remContact(listType
, str(userHandle
))
224 self
.savedEvents
.remContacts
.append((listType
, str(userHandle
)))
227 """ Shuts down the whole connection. Don't try to call any
228 other methods after this one. Except maybe connect() """
229 if self
.notificationClient
:
230 self
.notificationClient
.logOut()
231 for c
in self
.connectors
:
233 if self
.notificationFactory
:
234 self
.notificationFactory
.stopTrying()
235 self
.notificationFactory
.msncon
= None
237 for sbs
in self
.switchboardSessions
.values():
238 if hasattr(sbs
, "transport") and sbs
.transport
:
239 sbs
.transport
.loseConnection()
240 self
.switchboardSessions
= {}
242 self
.timeout
.cancel()
244 LogEvent(INFO
, self
.ident
)
248 def connectionFailed(self
, reason
=''):
249 """ Called when the connection to the server failed. """
251 def loginFailed(self
, reason
=''):
252 """ Called when the account could not be logged in. """
254 def connectionLost(self
, reason
=''):
255 """ Called when we are disconnected. """
257 def multipleLogin(self
):
258 """ Called when the server says there has been another login
259 for this account. """
261 def serverGoingDown(self
):
262 """ Called when the server says that it will be going down. """
264 def accountNotVerified(self
):
265 """ Called if this passport has not been verified. Certain
266 functions are not available. """
268 def userMapping(self
, passport
, jid
):
269 """ Called when it is brought to our attention that one of the
270 MSN contacts has a Jabber ID. You should communicate with Jabber. """
273 """ Called when we have authenticated, but before we receive
274 the contact list. """
276 def listSynchronized(self
):
277 """ Called when we have received the contact list. All methods
278 in this class are now valid. """
280 def ourStatusChanged(self
, statusCode
, screenName
, personal
):
281 """ Called when the user's status has changed. """
283 def gotMessage(self
, userHandle
, text
):
284 """ Called when a contact sends us a message """
286 def gotGroupchat(self
, msnGroupchat
, userHandle
):
287 """ Called when a conversation with more than one contact begins.
288 userHandle is the person who invited us.
289 The overriding method is expected to set msnGroupchat.groupchat to an object
290 that implements the following methods:
291 contactJoined(userHandle)
292 contactLeft(userHandle)
293 gotMessage(userHandle, text)
295 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
298 def gotContactTyping(self
, userHandle
):
299 """ Called when a contact sends typing notification.
300 Will be called once every 5 seconds. """
302 def failedMessage(self
, userHandle
, text
):
303 """ Called when a message we sent has been bounced back. """
305 def contactAvatarChanged(self
, userHandle
, hash):
306 """ Called when we receive a changed avatar hash for a contact.
307 You should call sendAvatarRequest(). """
309 def contactStatusChanged(self
, userHandle
):
310 """ Called when we receive status information for a contact. """
312 def gotFileReceive(self
, fileReceive
):
313 """ Called when a contact sends the user a file.
314 Call accept(fileHandle) or reject() on the object. """
316 def contactAddedMe(self
, userHandle
):
317 """ Called when a contact adds the user to their list. """
319 def contactRemovedMe(self
, userHandle
):
320 """ Called when a contact removes the user from their list. """
322 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
323 """ Received at login to tell about the user's Hotmail status """
325 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
326 """ Received in realtime whenever an email comes into the hotmail account """
328 def gotMSNAlert(self
, body
, action
, subscr
):
329 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
330 text of the alert. 'action' is a url for more information,
331 'subscr' is a url to modify your your alerts subscriptions. """
333 def gotAvatarImageData(self
, userHandle
, imageData
):
334 """ An contact's avatar has been received because a switchboard
335 session with them was started. """
343 self
.avatarImageData
= ""
344 self
.addContacts
= []
345 self
.remContacts
= []
347 def send(self
, msncon
):
348 if self
.avatarImageData
:
349 msncon
.notificationClient
.changeAvatar(self
.avatarImageData
, push
=False)
350 if self
.screenName
or self
.statusCode
or self
.personal
:
351 msncon
.changeStatus(self
.statusCode
, self
.screenName
, self
.personal
)
352 for listType
, userHandle
in self
.addContacts
:
353 msncon
.addContact(listType
, userHandle
)
354 for listType
, userHandle
in self
.remContacts
:
355 msncon
.remContact(listType
, userHandle
)
359 class DispatchClient(msn
.DispatchClient
):
360 def gotNotificationReferral(self
, host
, port
):
361 if self
.factory
.d
.called
: return # Too slow! We've already timed out
362 self
.factory
.d
.callback((host
, port
))
365 class NotificationClient(msn
.NotificationClient
):
366 def doDisconnect(self
, *args
):
367 if hasattr(self
, "transport") and self
.transport
:
368 self
.transport
.loseConnection()
370 def loginFailure(self
, message
):
371 self
.factory
.msncon
.loginFailed(message
)
373 def loggedIn(self
, userHandle
, verified
):
374 LogEvent(INFO
, self
.factory
.msncon
.ident
)
375 msn
.NotificationClient
.loggedIn(self
, userHandle
, verified
)
376 self
.factory
.msncon
._notificationClientReady
(self
)
377 self
.factory
.msncon
.loggedIn()
379 self
.factory
.msncon
.accountNotVerified()
382 msn
.NotificationClient
.logOut(self
)
383 # If we explicitly log out, then all of these events
385 self
.loginFailure
= self
.doDisconnect
386 self
.loggedIn
= self
.doDisconnect
387 self
.connectionLost
= self
.doDisconnect
389 def connectionLost(self
, reason
):
390 if not self
.factory
.msncon
:
391 # If MSNConnection.logOut is called before _notificationClientReady
395 LogEvent(INFO
, self
.factory
.msncon
.ident
)
396 msn
.NotificationClient
.connectionLost(self
, reason
)
397 if self
.factory
.maxRetries
> self
.factory
.retries
:
398 self
.factory
.stopTrying()
399 self
.factory
.msncon
.connectionLost(reason
)
400 # Make sure this event is handled after any others
401 reactor
.callLater(0, wait
)
403 def gotMSNAlert(self
, body
, action
, subscr
):
404 LogEvent(INFO
, self
.factory
.msncon
.ident
)
405 self
.factory
.msncon
.gotMSNAlert(body
, action
, subscr
)
407 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
408 LogEvent(INFO
, self
.factory
.msncon
.ident
)
409 self
.factory
.msncon
.gotInitialEmailNotification(inboxunread
, foldersunread
)
411 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
412 LogEvent(INFO
, self
.factory
.msncon
.ident
)
413 self
.factory
.msncon
.gotRealtimeEmailNotification(mailfrom
, fromaddr
, subject
)
415 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
416 LogEvent(INFO
, self
.factory
.msncon
.ident
)
417 self
.factory
.msncon
.contactAddedMe(userHandle
)
419 def userRemovedMe(self
, userHandle
):
420 LogEvent(INFO
, self
.factory
.msncon
.ident
)
421 self
.factory
.msncon
.contactRemovedMe(userHandle
)
423 def listSynchronized(self
, *args
):
424 LogEvent(INFO
, self
.factory
.msncon
.ident
)
425 self
.factory
.msncon
._sendSavedEvents
()
426 self
.factory
.msncon
.listSynchronized()
428 def contactAvatarChanged(self
, userHandle
, hash):
429 LogEvent(INFO
, self
.factory
.msncon
.ident
)
430 self
.factory
.msncon
.contactAvatarChanged(userHandle
, hash)
432 def gotContactStatus(self
, userHandle
, statusCode
, screenName
):
433 LogEvent(INFO
, self
.factory
.msncon
.ident
)
434 self
.factory
.msncon
.contactStatusChanged(userHandle
)
436 def contactStatusChanged(self
, userHandle
, statusCode
, screenName
):
437 LogEvent(INFO
, self
.factory
.msncon
.ident
)
438 self
.factory
.msncon
.contactStatusChanged(userHandle
)
440 def contactPersonalChanged(self
, userHandle
, personal
):
441 LogEvent(INFO
, self
.factory
.msncon
.ident
)
442 self
.factory
.msncon
.contactStatusChanged(userHandle
)
444 def contactOffline(self
, userHandle
):
445 LogEvent(INFO
, self
.factory
.msncon
.ident
)
446 self
.factory
.msncon
.contactStatusChanged(userHandle
)
448 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
449 LogEvent(INFO
, self
.factory
.msncon
.ident
)
450 sb
= self
.factory
.msncon
.switchboardSessions
.get(userHandle
)
451 if sb
and sb
.transport
:
452 sb
.transport
.loseConnection()
453 sb
= OneSwitchboardSession(self
.factory
.msncon
, userHandle
)
454 self
.factory
.msncon
.switchboardSessions
[userHandle
] = sb
455 sb
.connectReply(host
, port
, key
, sessionID
)
457 def multipleLogin(self
):
458 LogEvent(INFO
, self
.factory
.msncon
.ident
)
459 self
.factory
.msncon
.multipleLogin()
461 def serverGoingDown(self
):
462 LogEvent(INFO
, self
.factory
.msncon
.ident
)
463 self
.factory
.msncon
.serverGoingDown()
467 class SwitchboardSessionBase(msn
.SwitchboardClient
):
468 def __init__(self
, msncon
):
469 msn
.SwitchboardClient
.__init
__(self
)
471 self
.msnobj
= msncon
.notificationClient
.msnobj
472 self
.userHandle
= msncon
.username
473 self
.ident
= (msncon
.ident
, "INVALID!!")
474 self
.messageBuffer
= []
478 def connectionLost(self
, reason
):
479 msn
.SwitchboardClient
.connectionLost(self
, reason
)
480 LogEvent(INFO
, self
.ident
)
484 self
.ident
= (self
.ident
[0], self
.ident
[1], "Disconnected!")
487 LogEvent(INFO
, self
.ident
)
492 LogEvent(INFO
, self
.ident
)
494 def sbRequestAccepted((host
, port
, key
)):
495 LogEvent(INFO
, self
.ident
)
498 factory
= ClientFactory()
499 factory
.buildProtocol
= lambda addr
: self
500 reactor
.connectTCP(host
, port
, factory
)
501 def sbRequestFailed(ignored
=None):
502 LogEvent(INFO
, self
.ident
)
503 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
504 d
= self
.msncon
.notificationClient
.requestSwitchboardServer()
505 d
.addCallbacks(sbRequestAccepted
, sbRequestFailed
)
507 def connectReply(self
, host
, port
, key
, sessionID
):
508 LogEvent(INFO
, self
.ident
)
511 self
.sessionID
= sessionID
513 factory
= ClientFactory()
514 factory
.buildProtocol
= lambda addr
: self
515 reactor
.connectTCP(host
, port
, factory
)
517 def flushBuffer(self
):
518 for message
, noerror
in self
.messageBuffer
[:]:
519 self
.messageBuffer
.remove((message
, noerror
))
520 self
.sendMessage(message
, noerror
)
521 for f
in self
.funcBuffer
[:]:
522 self
.funcBuffer
.remove(f
)
525 def failedMessage(self
, *ignored
):
526 raise NotImplementedError
528 def sendClientCaps(self
):
529 message
= msn
.MSNMessage()
530 message
.setHeader("Content-Type", "text/x-clientcaps")
531 message
.setHeader("Client-Name", "PyMSNt")
532 if hasattr(self
.msncon
, "jabberID"):
533 message
.setHeader("JabberID", str(self
.msncon
.jabberID
))
534 self
.sendMessage(message
)
536 def sendMessage(self
, message
, noerror
=False):
537 # Check to make sure that clientcaps only gets sent after
538 # the first text type message.
539 if isinstance(message
, msn
.MSNMessage
) and message
.getHeader("Content-Type").startswith("text"):
540 self
.sendMessage
= self
.sendMessageReal
541 self
.sendClientCaps()
542 return self
.sendMessage(message
, noerror
)
544 return self
.sendMessageReal(message
, noerror
)
546 def sendMessageReal(self
, text
, noerror
=False):
547 if not isinstance(text
, basestring
):
548 msn
.SwitchboardClient
.sendMessage(self
, text
)
551 self
.messageBuffer
.append((text
, noerror
))
553 LogEvent(INFO
, self
.ident
)
554 text
= str(text
.replace("\n", "\r\n").encode("utf-8"))
555 def failedMessage(ignored
):
557 self
.failedMessage(text
)
559 if len(text
) < MSNConnection
.MAXMESSAGESIZE
:
560 message
= msn
.MSNMessage(message
=text
)
561 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
563 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
565 d
.addCallbacks(failedMessage
, failedMessage
)
568 chunks
= int(math
.ceil(len(text
) / float(MSNConnection
.MAXMESSAGESIZE
)))
570 guid
= msn
.random_guid()
571 while chunk
< chunks
:
572 offset
= chunk
* MSNConnection
.MAXMESSAGESIZE
573 message
= msn
.MSNMessage(message
=text
[offset
: offset
+ MSNConnection
.MAXMESSAGESIZE
])
574 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
575 message
.setHeader("Message-ID", guid
)
577 message
.setHeader("Chunks", str(chunks
))
579 message
.delHeader("MIME-Version")
580 message
.delHeader("Content-Type")
581 message
.setHeader("Chunk", str(chunk
))
583 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
585 d
.addCallbacks(failedMessage
, failedMessage
)
590 class MultiSwitchboardSession(SwitchboardSessionBase
):
591 """ Create one of me to chat to multiple contacts """
593 def __init__(self
, msncon
):
594 """ Automatically creates a new switchboard connection to the server """
595 SwitchboardSessionBase
.__init
__(self
, msncon
)
596 self
.ident
= (self
.msncon
.ident
, repr(self
))
597 self
.contactCount
= 0
598 self
.groupchat
= None
601 def failedMessage(self
, text
):
602 self
.groupchat
.gotMessage("BOUNCE", text
)
604 def sendMessage(self
, text
, noerror
=False):
605 """ Used to send a mesage to the groupchat. Can be called immediately
606 after instantiation. """
607 if self
.contactCount
> 0:
608 SwitchboardSessionBase
.sendMessage(self
, text
, noerror
)
610 #self.messageBuffer.append((message, noerror))
611 pass # They're sending messages to an empty room. Ignore.
613 def inviteUser(self
, userHandle
):
614 """ Used to invite a contact to the groupchat. Can be called immediately
615 after instantiation. """
616 userHandle
= str(userHandle
)
618 LogEvent(INFO
, self
.ident
, "immediate")
619 msn
.SwitchboardClient
.inviteUser(self
, userHandle
)
621 LogEvent(INFO
, self
.ident
, "pending")
622 self
.funcBuffer
.append(lambda: msn
.SwitchboardClient
.inviteUser(self
, userHandle
))
624 def gotMessage(self
, message
):
625 self
.groupchat
.gotMessage(message
.userHandle
, message
.getMessage())
627 def userJoined(self
, userHandle
, screenName
=''):
628 LogEvent(INFO
, self
.ident
)
629 self
.contactCount
+= 1
630 self
.groupchat
.contactJoined(userHandle
)
632 def userLeft(self
, userHandle
):
633 LogEvent(INFO
, self
.ident
)
634 self
.contactCount
-= 1
635 self
.groupchat
.contactLeft(userHandle
)
639 class OneSwitchboardSession(SwitchboardSessionBase
):
640 def __init__(self
, msncon
, remoteUser
):
641 SwitchboardSessionBase
.__init
__(self
, msncon
)
642 self
.remoteUser
= str(remoteUser
)
643 self
.ident
= (self
.msncon
.ident
, self
.remoteUser
)
644 self
.chattingUsers
= []
647 def connectionLost(self
, reason
):
649 self
.timeout
.cancel()
651 for message
, noerror
in self
.messageBuffer
:
653 self
.failedMessage(message
)
654 self
.messageBuffer
= []
656 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
657 # Unexpected disconnection. Must remove us from msncon
658 self
.msncon
.switchboardSessions
.pop(self
.remoteUser
)
660 SwitchboardSessionBase
.connectionLost(self
, reason
)
663 LogEvent(INFO
, self
.ident
)
665 for user
in self
.chattingUsers
:
666 self
.userJoined(user
)
668 self
.timeout
.cancel()
672 def _switchToMulti(self
, userHandle
):
673 LogEvent(INFO
, self
.ident
)
674 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
675 self
.__class
__ = MultiSwitchboardSession
677 self
.contactCount
= 0
678 self
.msncon
.gotGroupchat(self
, userHandle
)
679 assert self
.groupchat
681 def failedMessage(self
, text
):
682 self
.msncon
.failedMessage(self
.remoteUser
, text
)
686 LogEvent(INFO
, self
.ident
)
688 def failCB(arg
=None):
689 if not (self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
)):
691 LogEvent(INFO
, self
.ident
, "User has not joined after 30 seconds.")
692 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
694 self
.transport
.loseConnection()
695 d
= self
.inviteUser(self
.remoteUser
)
697 self
.timeout
= reactor
.callLater(30.0, failCB
)
701 def gotChattingUsers(self
, users
):
702 for userHandle
in users
.keys():
703 self
.chattingUsers
.append(userHandle
)
705 def userJoined(self
, userHandle
, screenName
=''):
706 LogEvent(INFO
, self
.ident
)
709 if userHandle
!= self
.remoteUser
:
710 # Another user has joined, so we now have three participants.
711 remoteUser
= self
.remoteUser
712 self
._switchToMulti
(remoteUser
)
713 self
.userJoined(remoteUser
)
714 self
.userJoined(userHandle
)
716 def updateAvatarCB((imageData
, )):
718 self
.msncon
.gotAvatarImageData(self
.remoteUser
, imageData
)
719 d
= self
.sendAvatarRequest()
721 d
.addCallback(updateAvatarCB
)
723 def userLeft(self
, userHandle
):
725 if userHandle
== self
.remoteUser
:
726 if self
.msncon
and self
.msncon
.switchboardSessions
.has_key(self
.remoteUser
):
727 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
728 reactor
.callLater(0, wait
) # Make sure this is handled after everything else
730 def gotMessage(self
, message
):
731 LogEvent(INFO
, self
.ident
)
732 cTypes
= [s
.strip() for s
in message
.getHeader("Content-Type").split(';')]
733 if "text/plain" == cTypes
[0]:
735 if len(cTypes
) > 1 and cTypes
[1].lower().find("utf-8") >= 0:
736 text
= message
.getMessage().decode("utf-8")
738 text
= message
.getMessage()
739 self
.msncon
.gotMessage(self
.remoteUser
, text
)
740 except UnicodeDecodeError:
741 LogEvent(WARN
, self
.ident
, "Message lost!")
742 self
.msncon
.gotMessage(self
.remoteUser
, "A message was lost.")
744 elif "text/x-clientcaps" == cTypes
[0]:
745 if message
.hasHeader("JabberID"):
746 jid
= message
.getHeader("JabberID")
747 self
.msncon
.userMapping(message
.userHandle
, jid
)
749 LogEvent(INFO
, self
.ident
, "Discarding unknown message type.")
751 def gotFileReceive(self
, fileReceive
):
752 LogEvent(INFO
, self
.ident
)
753 self
.msncon
.gotFileReceive(fileReceive
)
755 def gotContactTyping(self
, message
):
756 LogEvent(INFO
, self
.ident
)
757 self
.msncon
.gotContactTyping(message
.userHandle
)
759 def sendTypingNotification(self
):
760 LogEvent(INFO
, self
.ident
)
762 msn
.SwitchboardClient
.sendTypingNotification(self
)
764 CAPS
= msn
.MSNContact
.MSNC1 | msn
.MSNContact
.MSNC2 | msn
.MSNContact
.MSNC3 | msn
.MSNContact
.MSNC4
765 def sendAvatarRequest(self
):
766 if not self
.ready
: return
767 msnContacts
= self
.msncon
.getContacts()
768 if not msnContacts
: return
769 msnContact
= msnContacts
.getContact(self
.remoteUser
)
770 if not (msnContact
and msnContact
.caps
& self
.CAPS
and msnContact
.msnobj
): return
771 if msnContact
.msnobjGot
: return
772 msnContact
.msnobjGot
= True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
773 return msn
.SwitchboardClient
.sendAvatarRequest(self
, msnContact
)
775 def sendFile(self
, msnContact
, filename
, filesize
):
776 def doSendFile(ignored
=None):
777 d
.callback(msn
.SwitchboardClient
.sendFile(self
, msnContact
, filename
, filesize
))
780 reactor
.callLater(0, doSendFile
)
782 self
.funcBuffer
.append(doSendFile
)